Telegram bots are one of the most practical things you can deploy on a VPS. They’re lightweight, always-on services that need a stable connection, a public IP, and a process that doesn’t die when you close your terminal. A VPS checks all three boxes — and once you’ve done this once, you’ll wonder why you ever tried running a bot on your laptop.
This guide walks you through the entire process: from creating a bot to deploying it on a VPS with proper process management, HTTPS webhooks, and automatic restarts. We’ll use Python and the popular python-telegram-bot library, but the infrastructure concepts apply equally to Node.js, Go, or any other stack.
What You’ll Need
- A VPS running Ubuntu 22.04 or 24.04 (any plan with 1 vCPU and 1Gb RAM is sufficient for most bots)
- A domain name or a dedicated IP address
- A Telegram account to create the bot
- Basic familiarity with the Linux command line
Step 1: Create Your Bot with BotFather
Every Telegram bot starts with BotFather — Telegram’s official bot for creating and managing bots.
- Open Telegram and search for
@BotFather - Send
/newbot - Choose a display name (e.g., My Awesome Bot)
- Choose a username ending in
bot(e.g.,myawesomebot) - BotFather will reply with your bot token — a string that looks like
7123456789:AAF...
Copy this token and keep it safe. Anyone with this token can control your bot.
Step 2: Connect to Your VPS
Connect via SSH:
ssh root@your-server-ip
It’s good practice to create a dedicated user for running your bot rather than using root:
adduser botuser
usermod -aG sudo botuser
su - botuser
Step 3: Install Python and Dependencies
Ubuntu 22.04 and 24.04 come with Python 3 pre-installed. Verify:
python3 --version
Install pip and venv:
sudo apt update
sudo apt install python3-pip python3-venv -y
Create a project directory and a virtual environment:
mkdir ~/mybot && cd ~/mybot
python3 -m venv venv
source venv/bin/activate
Install the Telegram bot library:
pip install python-telegram-bot
Step 4: Write Your Bot
Create the bot script:
nano bot.py
Here’s a minimal but functional bot that responds to /start and echoes any text message:
#python
import logging
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Hello! I'm running on a VPS 24/7.")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(update.message.text)
if __name__ == "__main__":
app = ApplicationBuilder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
app.run_polling()
Replace YOUR_BOT_TOKEN_HERE with the token from BotFather.
Test it locally first:
python bot.py
Open Telegram, find your bot, and send /start. If it responds, the code works. Press Ctrl+C to stop it.
Step 5: Keep It Running with systemd
Running your bot manually in a terminal means it dies the moment you disconnect. The right solution is systemd — the process manager built into every modern Linux distribution. It will start your bot automatically on boot and restart it if it crashes.
Create a service file:
sudo nano /etc/systemd/system/mybot.service
Paste the following (adjust paths if your username isn’t botuser):
[Unit]
Description=Telegram Bot
After=network.target
[Service]
Type=simple
User=botuser
WorkingDirectory=/home/botuser/mybot
ExecStart=/home/botuser/mybot/venv/bin/python bot.py
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable mybot
sudo systemctl start mybot
Check that it’s running:
sudo systemctl status mybot
You should see active (running). Your bot now starts automatically on every reboot and restarts within 5 seconds if it crashes.
To view logs:
journalctl -u mybot -f
Step 6: Polling vs Webhooks — Which Should You Use?
Your bot can receive updates from Telegram in two ways:
Polling (what the example above uses): Your bot constantly asks Telegram “any new messages?” every few seconds. Simple to set up, no SSL required, works behind NAT. Fine for development and low-traffic bots.
Webhooks: Telegram pushes updates to your server instantly the moment a message arrives. Faster response times, more efficient, and scales better. Requires a public HTTPS endpoint — meaning you need a domain and an SSL certificate.
For a production bot that needs to feel responsive, webhooks are the right choice.
Step 7: Setting Up Webhooks with SSL (Production Setup)
7.1 Point a Domain to Your VPS
In your DNS settings, create an A record pointing your domain (or a subdomain like bot.yourdomain.com) to your VPS IP address. Allow a few minutes for propagation.
7.2 Install Nginx and Certbot
sudo apt install nginx certbot python3-certbot-nginx -y
7.3 Create an Nginx Config
sudo nano /etc/nginx/sites-available/mybot
server {
listen 80;
server_name bot.yourdomain.com;
location /webhook {
proxy_pass http://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Enable the config:
sudo ln -s /etc/nginx/sites-available/mybot /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
7.4 Get an SSL Certificate
sudo certbot --nginx -d bot.yourdomain.com
Certbot will automatically update your Nginx config with HTTPS and set up auto-renewal.
7.5 Update Your Bot to Use Webhooks
Update bot.py:
#python
import logging
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, MessageHandler, filters, ContextTypes
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
BOT_TOKEN = "YOUR_BOT_TOKEN_HERE"
WEBHOOK_URL = "https://bot.yourdomain.com/webhook"
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text("Hello! Running on VPS with webhooks.")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(update.message.text)
if __name__ == "__main__":
app = ApplicationBuilder().token(BOT_TOKEN).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
app.run_webhook(
listen="0.0.0.0",
port=8443,
webhook_url=WEBHOOK_URL
)
Restart the service:
sudo systemctl restart mybot
Step 8: Store Secrets Properly
Hardcoding your bot token in bot.py is fine for testing but bad practice for anything you’ll commit to version control or share. Use environment variables instead.
Create a .env file:
nano /home/botuser/mybot/.env
BOT_TOKEN=7123456789:AAF...
Install python-dotenv:
pip install python-dotenv
Update bot.py:
#python
import os
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
Restrict permissions on the .env file:
chmod 600 /home/botuser/mybot/.env
Step 9: Basic Firewall Setup
Lock down your VPS to only allow the traffic you need:
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
If you’re using polling (no Nginx), also allow the bot’s port:
sudo ufw allow 8443
Step 10: Monitoring and Updates
Check bot status at any time:
sudo systemctl status mybot
View live logs:
journalctl -u mybot -f
Deploy an update:
cd ~/mybot
# edit bot.py or pull from git
sudo systemctl restart mybot
Set up a Git-based workflow (optional but recommended): push your bot code to a private Git repository and pull updates to the VPS when you deploy. This gives you version history, easy rollbacks, and a clean separation between development and production.
Troubleshooting Common Issues
Bot doesn’t respond after deploy Check the logs: journalctl -u mybot -n 50. Most issues are import errors or a wrong bot token.
Webhook not receiving updates Verify the SSL certificate is valid, Nginx is running, and the webhook URL matches exactly what Telegram expects. You can check the current webhook status via the Telegram Bot API:
https://api.telegram.org/bot<TOKEN>/getWebhookInfo
Bot crashes on restart If the bot crashes immediately at start, systemd will retry every 5 seconds. Check logs to find the root cause before it fills your journal.
Port already in use If you see Address already in use, another process is on port 8443. Find it with sudo lsof -i :8443 and stop it.
Scaling Up
A basic bot on a 1 vCPU / 1Gb VPS will handle thousands of users without breaking a sweat. But if your bot grows — adding databases, image processing, external API calls, or multiple concurrent workflows — you’ll want to think about:
- A database: PostgreSQL or SQLite for storing user state, conversation history, or application data
- Redis: For job queues, rate limiting, and caching
- More RAM: Especially if you’re running multiple bots or heavy dependencies on the same server
- Multiple bots on one VPS: Each gets its own systemd service and Nginx location block — straightforward to manage
Hosting.international’s KVM VPS plans let you scale CPU and RAM without migrating to a new server, so you can start small and grow as your bot’s user base does.
Summary
| Step | What you did |
|---|---|
| 1 | Created a bot token with BotFather |
| 2 | Connected to VPS via SSH |
| 3 | Set up Python and a virtual environment |
| 4 | Wrote and tested the bot locally |
| 5 | Configured systemd for always-on operation |
| 6 | Chose between polling and webhooks |
| 7 | Set up Nginx + SSL for webhook mode |
| 8 | Stored secrets in environment variables |
| 9 | Configured UFW firewall |
| 10 | Established a deployment and monitoring workflow |
A VPS is the cleanest way to run a Telegram bot in production. You get a stable IP, 24/7 uptime, full control over the environment, and none of the cold-start latency of serverless platforms.
