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…
- 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
aptandsystemd. - 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
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
/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
.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
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"
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?
Should I use polling or webhooks?
How do I keep the bot running after I close the SSH session?
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?
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?
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?
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), 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.