A Telegram bot on your laptop stops when the laptop sleeps. On a VPS, it runs around the clock, restarts automatically after crashes or reboots, and can reach your other services. Here’s a clean production setup with Python.
1. Create the bot
In Telegram, open @BotFather, send /newbot, choose a name and username, and copy the API token. Treat the token like a password — anyone with it controls your bot.
2. Prepare the server
apt update
apt install -y python3 python3-venv
adduser --system --group --home /opt/mybot mybot
3. Write the bot
cd /opt/mybot
python3 -m venv venv
./venv/bin/pip install "python-telegram-bot>=21,<23"
/opt/mybot/bot.py:
import logging
import os
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
logging.basicConfig(format="%(asctime)s %(levelname)s %(name)s: %(message)s", level=logging.INFO)
log = logging.getLogger("mybot")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text("Hi! I'm running on a VPS. Send me any text.")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
await update.message.reply_text(update.message.text)
def main() -> None:
app = Application.builder().token(os.environ["BOT_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
log.info("Bot starting (long polling)")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
Store the token outside the code:
echo "BOT_TOKEN=123456:ABC..." > /opt/mybot/.env
chmod 600 /opt/mybot/.env
chown -R mybot: /opt/mybot
Test it once in the foreground:
sudo -u mybot env $(cat /opt/mybot/.env) /opt/mybot/venv/bin/python /opt/mybot/bot.py
4. Run it as a systemd service
/etc/systemd/system/mybot.service:
[Unit]
Description=Telegram bot
After=network-online.target
Wants=network-online.target
[Service]
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/opt/mybot/.env
ExecStart=/opt/mybot/venv/bin/python /opt/mybot/bot.py
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/mybot
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now mybot
journalctl -u mybot -f
The bot now starts at boot and restarts within 5 seconds if it crashes.
5. Polling or webhooks?
- Long polling (above): the bot asks Telegram for updates. No domain, no open ports, works anywhere. Ideal for most bots.
- Webhooks: Telegram pushes updates to your HTTPS endpoint. Slightly lower latency and better for high-traffic bots, but needs a domain and TLS.
For webhooks, run the bot’s built-in web server on localhost and put Nginx in front:
app.run_webhook(
listen="127.0.0.1",
port=8443,
url_path="telegram",
secret_token=os.environ["WEBHOOK_SECRET"],
webhook_url="https://bot.example.com/telegram",
)
location /telegram {
proxy_pass http://127.0.0.1:8443/telegram;
}
The secret_token makes Telegram send a header your bot verifies, so others can’t post fake updates.
6. Good practices
- Keep secrets in the env file, never in Git.
- Handle errors with an error handler (
app.add_error_handler) so one bad update doesn’t stop the bot. - Persist data in SQLite or PostgreSQL, not in memory.
- Respect rate limits — Telegram limits how fast bots can send messages; queue bulk messages.
- Update dependencies regularly (
pip install -U), testing first.
Ideas
- Server monitoring alerts to a chat.
- A front end to a self-hosted AI model — see Ollama on a VPS.
- Notifications from CI/CD, backups or Vikunja tasks.
Hosting
A bot needs very little: the smallest Ubuntu VPS runs several bots. Telegram’s servers are reachable from all our locations; pick the one closest to the APIs your bot calls. Related: Node.js on a VPS if you prefer JavaScript.