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.

  • Telegram
  • Python
  • systemd