DEPLOY May 15, 2026 9 min read

How to Deploy a Telegram Bot to a VPS (Step-by-Step)

TL;DR A $4–6/mo VPS (1 vCPU, 1 GB RAM) handles most Telegram bots comfortably. Use systemd to manage the bot process — automatic restarts, logging, boot persistence. Webhook mode is…

by Bugi 9 min
TL;DR

  • A $4–6/mo VPS (1 vCPU, 1 GB RAM) handles most Telegram bots comfortably.
  • Use systemd to manage the bot process — automatic restarts, logging, boot persistence.
  • Webhook mode is more efficient than polling on a server you control.

Overview

This guide walks through deploying a Telegram bot to a Linux VPS — from initial server setup to a production-ready process managed by systemd. The bot runs as a background service that survives reboots, restarts on crashes, and logs to the journal.

Total time: 20–30 minutes. You need a VPS running Ubuntu 22.04+ or Debian 12+, a domain name (optional, required for webhooks), and a bot token from @BotFather.

The instructions apply to bots written in Python (python-telegram-bot, aiogram) or Node.js (telegraf, grammY). The deployment pattern is identical — only the runtime commands differ.

What You’ll Need

  • VPS — any provider (Hetzner, DigitalOcean, Linode, Vultr). Minimum: 1 vCPU, 1 GB RAM, 20 GB disk.
  • OS — Ubuntu 22.04 LTS or Debian 12. This guide uses apt and systemd.
  • Bot token — from Telegram’s @BotFather.
  • Domain name — only if using webhook mode. Polling mode works without one.
  • SSH access — root or a sudo-capable user.
  • Local copy of your bot code — tested and working on your machine.

Step 1: Provision and Secure the Server

1
Create a non-root user
Never run a bot as root. Create a dedicated user with sudo access.
2
Configure SSH key auth
Disable password login after confirming key-based access works.
3
Enable the firewall
Allow only SSH (22), HTTP (80), and HTTPS (443).

SSH into your new server and run:

# Create a user for the bot
adduser botuser
usermod -aG sudo botuser

# Set up firewall
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

# Update packages
apt update && apt upgrade -y

Copy your SSH key to the new user:

ssh-copy-id botuser@your-server-ip
Warning
Disable root SSH login and password authentication in /etc/ssh/sshd_config after confirming key-based access works for your new user.

From this point, all commands run as botuser.

Step 2: Install the Runtime

For Python bots:

sudo apt install python3 python3-pip python3-venv -y

For Node.js bots:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install nodejs -y

Verify the installation:

python3 --version   # Python 3.10+
node --version      # v20.x

Step 3: Upload and Configure the Bot

Transfer your bot code to the server:

# From your local machine
scp -r ./my-bot botuser@your-server-ip:~/my-bot

On the server, set up the project:

Python:

cd ~/my-bot
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

Node.js:

cd ~/my-bot
npm ci --production

Store the Bot Token Securely

Create an environment file that systemd will load. This keeps secrets out of your code and shell history.

sudo mkdir -p /etc/my-bot
sudo tee /etc/my-bot/.env > /dev/null <<EOF
BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
EOF
sudo chmod 600 /etc/my-bot/.env
sudo chown botuser:botuser /etc/my-bot/.env
Danger
Never commit your .env file to git, paste tokens into command arguments, or store them in world-readable files. chmod 600 ensures only the owner can read it.

Update your bot code to read BOT_TOKEN from the environment:

# Python
import os
TOKEN = os.environ["BOT_TOKEN"]
// Node.js
const token = process.env.BOT_TOKEN;

Step 4: Create a systemd Service

This is the core of a production deployment. systemd handles process lifecycle — starting on boot, restarting on failure, and collecting logs.

sudo tee /etc/systemd/system/my-bot.service > /dev/null <<EOF
[Unit]
Description=Telegram Bot
After=network.target

[Service]
Type=simple
User=botuser
WorkingDirectory=/home/botuser/my-bot
EnvironmentFile=/etc/my-bot/.env
ExecStart=/home/botuser/my-bot/venv/bin/python3 bot.py
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

For Node.js, change the ExecStart line:

ExecStart=/usr/bin/node /home/botuser/my-bot/index.js

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable my-bot
sudo systemctl start my-bot
Takeaway

Restart=on-failure with RestartSec=10 means systemd waits 10 seconds before restarting a crashed bot — enough to avoid hammering the Telegram API with rapid reconnects.

Step 5: Verify the Deployment

Check the service status and logs:

~/my-bot

$ sudo systemctl status my-bot
 my-bot.service - Telegram Bot
     Active: active (running) since Thu 2026-05-15 10:23:01 UTC
   Main PID: 4821 (python3)
$ journalctl -u my-bot -f --no-pager
May 15 10:23:02 vps my-bot: Bot started, polling...
 Listening for updates

Send a message to your bot on Telegram. If it responds, the deployment is live.

Quick health-check commands:

# Is the process running?
systemctl is-active my-bot

# Last 50 log lines
journalctl -u my-bot -n 50 --no-pager

# Restart after a code update
sudo systemctl restart my-bot

Setting Up Webhooks (Optional)

Polling works but wastes resources — your bot constantly asks Telegram “any new messages?” Webhooks flip this: Telegram pushes updates to your server.

Requirements: a domain name and TLS certificate.

Install Nginx and Certbot:

sudo apt install nginx certbot python3-certbot-nginx -y

Create an Nginx config:

server {
    server_name bot.yourdomain.com;

    location /webhook {
        proxy_pass http://127.0.0.1:8443;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
sudo ln -s /etc/nginx/sites-available/my-bot /etc/nginx/sites-enabled/
sudo certbot --nginx -d bot.yourdomain.com
sudo systemctl reload nginx

Set the webhook URL via the Telegram API:

curl -s "https://api.telegram.org/bot${BOT_TOKEN}/setWebhook?url=https://bot.yourdomain.com/webhook"
Tip
Telegram only delivers webhooks over HTTPS on ports 443, 80, 88, or 8443. Port 443 behind Nginx is the simplest setup.

Updating the Bot

Deploy code updates with a simple pull-and-restart:

cd ~/my-bot
git pull origin main

# Python: update dependencies if requirements.txt changed
source venv/bin/activate && pip install -r requirements.txt

# Node.js: update dependencies if package.json changed
npm ci --production

# Restart the service
sudo systemctl restart my-bot

# Verify
journalctl -u my-bot -n 20 --no-pager

For zero-downtime updates on webhook bots, consider running two instances behind Nginx and switching upstream targets. For polling bots, the few seconds of downtime during restart are acceptable — Telegram queues pending updates for 24 hours.

Troubleshooting

Bot starts but immediately exits

Check the logs: journalctl -u my-bot -n 100 --no-pager. Common causes:
– Missing environment variable — verify /etc/my-bot/.env exists and EnvironmentFile path matches.
– Wrong ExecStart path — use absolute paths only. Run which python3 or which node to confirm.
– Import error — you installed dependencies globally but the service uses a venv (or vice versa).

Bot runs locally but not on the server

The server likely has a different Python/Node version. Check version compatibility. Also verify that your requirements.txt or package.json includes all dependencies — you may have globally installed packages on your dev machine that aren’t declared.

Webhook returns 502 Bad Gateway

Nginx can’t reach the bot process. Confirm the bot is listening on the correct port (127.0.0.1:8443, not 0.0.0.0:8443 unless you want direct exposure). Check that the proxy_pass port matches your bot’s listening port.

systemd reports start-limit-hit

The bot crashed too many times in quick succession. Fix the underlying error first, then reset the counter:

sudo systemctl reset-failed my-bot
sudo systemctl start my-bot

FAQ

What’s the minimum VPS spec for a Telegram bot?
1 vCPU and 512 MB–1 GB RAM handles most bots serving hundreds of users. A Python polling bot at idle uses roughly 30–50 MB RAM. You only need more if you’re doing heavy computation, image processing, or running a database alongside the bot.
Should I use polling or webhooks?
On a VPS, webhooks are more efficient — Telegram pushes updates to you instead of your bot polling in a loop. However, polling is simpler to set up (no domain or TLS required) and fine for low-traffic bots. Start with polling and switch to webhooks when it matters.
How do I keep the bot running after I close the SSH session?
The systemd service handles this. Once you run sudo systemctl enable my-bot, the bot starts on boot and runs independently of any SSH session. Do not use screen, tmux, or nohup for production — systemd is the correct tool.
Can I run multiple bots on one VPS?
Yes. Create a separate systemd service file for each bot (e.g., bot-a.service, bot-b.service). Each gets its own EnvironmentFile, WorkingDirectory, and ExecStart. A 1 GB RAM VPS can comfortably run 3–5 lightweight Python bots.
How do I view bot logs?
Use journalctl -u my-bot -f to tail logs in real time. Add -n 100 to see the last 100 lines. Add --since "1 hour ago" to filter by time. All stdout and stderr from your bot process goes to the systemd journal automatically.
Do I need Docker for a Telegram bot?
No. Docker adds complexity without much benefit for a single bot on a single server. A virtualenv (Python) or npm ci (Node.js) plus systemd gives you isolated dependencies and process management with less overhead. Docker makes sense if you’re deploying multiple services or need reproducible builds across environments.
How do I handle bot token rotation?
Revoke the old token via @BotFather (/revoke), generate a new one, update /etc/my-bot/.env, and restart the service with sudo systemctl restart my-bot. The entire process takes under a minute.
What’s the minimum VPS spec for a Telegram bot?
1 vCPU and 512 MB–1 GB RAM handles most bots serving hundreds of users. A Python polling bot at idle uses roughly 30–50 MB RAM. You only need more if you’re doing heavy computation, image processing, or running a database alongside the bot.
Should I use polling or webhooks?
On a VPS, webhooks are more efficient — Telegram pushes updates to you instead of your bot polling in a loop. However, polling is simpler to set up (no domain or TLS required) and fine for low-traffic bots. Start with polling and switch to webhooks when it matters.
How do I keep the bot running after I close the SSH session?
The systemd service handles this. Once you run sudo systemctl enable my-bot, the bot starts on boot and runs independently of any SSH session. Do not use screen, tmux, or nohup for production — systemd is the correct tool.
Can I run multiple bots on one VPS?
Yes. Create a separate systemd service file for each bot. Each gets its own EnvironmentFile, WorkingDirectory, and ExecStart. A 1 GB RAM VPS can comfortably run 3–5 lightweight Python bots.
How do I view bot logs?
Use journalctl -u my-bot -f to tail logs in real time. Add -n 100 to see the last 100 lines. Add –since ‘1 hour ago’ to filter by time. All stdout and stderr from your bot process goes to the systemd journal automatically.
Do I need Docker for a Telegram bot?
No. Docker adds complexity without much benefit for a single bot on a single server. A virtualenv (Python) or npm ci (Node.js) plus systemd gives you isolated dependencies and process management with less overhead. Docker makes sense if you’re deploying multiple services or need reproducible builds across environments.
How do I handle bot token rotation?
Revoke the old token via @BotFather (/revoke), generate a new one, update /etc/my-bot/.env, and restart the service with sudo systemctl restart my-bot. The entire process takes under a minute.