How to Run a Telegram Bot on a VPS: 24/7 Uptime with Webhook Mode and Python
A Telegram bot running on your laptop only works while your laptop is on. A bot on a VPS runs 24/7, responds instantly via webhook (Telegram pushes updates to your server rather than your bot polling), and handles thousands of messages per hour. This guide builds and deploys a production Telegram bot with Python, webhook mode, Nginx SSL, and systemd for automatic restart.
Polling vs Webhook Mode
- Polling: Bot asks Telegram “any new messages?” on a schedule. Simple to develop locally, 1–2 second delay, wastes resources, no inbound port needed.
- Webhook: Telegram pushes each update to your HTTPS endpoint the moment it arrives. Zero latency, no polling overhead. Requires HTTPS. Use this in production.
Step 1: Create Your Bot with BotFather
- Open Telegram → search for
@BotFather - Send
/newbotand follow the prompts - Copy your bot token:
1234567890:ABCDefghijklMNOpqrstuvwxyz
Step 2: Set Up Python Environment on VPS
sudo apt install -y python3.12 python3.12-venv redis-server
sudo systemctl enable --now redis-server
mkdir -p /opt/mybot && cd /opt/mybot
python3.12 -m venv .venv
source .venv/bin/activate
pip install "python-telegram-bot[webhooks]" redis python-dotenv aiohttp
Step 3: Write the Bot
nano /opt/mybot/bot.py
"""Production Telegram bot with webhook mode."""
import logging
import os
from dotenv import load_dotenv
from telegram import Update
from telegram.ext import (
Application, CommandHandler, MessageHandler,
filters, ContextTypes,
)
load_dotenv('/opt/mybot/.env')
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger(__name__)
BOT_TOKEN = os.environ['BOT_TOKEN']
WEBHOOK_URL = os.environ['WEBHOOK_URL'] # e.g. https://bot.yourdomain.com
WEBHOOK_PATH = f'/bot/{BOT_TOKEN}'
PORT = int(os.environ.get('PORT', 8443))
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /start command."""
user = update.effective_user
await update.message.reply_html(
f'Hello {user.mention_html()}! I am your bot.\n'
'Send /help to see available commands.'
)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /help command."""
await update.message.reply_text(
'/start - Welcome message\n'
'/help - This help message\n'
'Send any text - I will echo it back'
)
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Echo the user message."""
await update.message.reply_text(f'Echo: {update.message.text}')
async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
logger.error('Exception while handling an update:', exc_info=context.error)
def main() -> None:
app = Application.builder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler('start', start))
app.add_handler(CommandHandler('help', help_command))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
app.add_error_handler(error_handler)
# Run webhook server
app.run_webhook(
listen='127.0.0.1',
port=PORT,
url_path=WEBHOOK_PATH,
webhook_url=f'{WEBHOOK_URL}{WEBHOOK_PATH}',
allowed_updates=Update.ALL_TYPES,
)
if __name__ == '__main__':
main()
nano /opt/mybot/.env
BOT_TOKEN=1234567890:ABCDefghijklMNOpqrstuvwxyz
WEBHOOK_URL=https://bot.yourdomain.com
PORT=8443
chmod 600 /opt/mybot/.env
Step 4: Nginx with SSL (Telegram Requires HTTPS)
sudo nano /etc/nginx/sites-available/telegram-bot
server {
listen 80;
server_name bot.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name bot.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/bot.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bot.yourdomain.com/privkey.pem;
# Telegram webhook path
location /bot/ {
proxy_pass http://127.0.0.1:8443;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/telegram-bot /etc/nginx/sites-enabled/
sudo certbot --nginx -d bot.yourdomain.com
sudo systemctl reload nginx
Step 5: systemd Service
sudo nano /etc/systemd/system/telegram-bot.service
[Unit]
Description=Telegram Bot
After=network.target redis.service
[Service]
User=deploy
WorkingDirectory=/opt/mybot
EnvironmentFile=/opt/mybot/.env
ExecStart=/opt/mybot/.venv/bin/python bot.py
Restart=on-failure
RestartSec=10s
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now telegram-bot
sudo systemctl status telegram-bot
sudo journalctl -u telegram-bot -f
Step 6: Verify Webhook Registration
curl "https://api.telegram.org/bot${BOT_TOKEN}/getWebhookInfo" | python3 -m json.tool
# Expected response:
# {
# "url": "https://bot.yourdomain.com/bot/YOUR_TOKEN",
# "has_custom_certificate": false,
# "pending_update_count": 0,
# "last_error_message": ""
# }
Conversation State with Redis
from redis import Redis
redis_client = Redis(host='localhost', port=6379, db=0, decode_responses=True)
async def handle_step1(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
redis_client.set(f'user:{user_id}:step', 'awaiting_email', ex=3600)
await update.message.reply_text('Please enter your email address:')
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
user_id = update.effective_user.id
step = redis_client.get(f'user:{user_id}:step')
if step == 'awaiting_email':
email = update.message.text
redis_client.set(f'user:{user_id}:email', email)
redis_client.delete(f'user:{user_id}:step')
await update.message.reply_text(f'Email {email} saved!')
else:
await update.message.reply_text(f'Echo: {update.message.text}')
Getting Started
A Telegram bot uses 50–100 MB RAM for a Python bot — even the smallest Ubuntu VPS at VPS.DO can host dozens of bots simultaneously. For bots that frequently call a database or internal API, co-locating the bot on the same VPS as those services eliminates network latency between the bot and its backends.
Conclusion
Deploying a Telegram bot on a VPS with webhook mode eliminates polling delays and local machine dependency — the bot responds instantly, runs 24/7, and survives VPS reboots via systemd. The Nginx SSL proxy handles Telegram’s HTTPS requirement, and Redis provides conversation state that persists across bot restarts. A well-designed Python Telegram bot on a small VPS handles thousands of messages per hour without approaching resource limits.