Blog / Tutorials

How to run a Telegram bot on a VPS 24/7

By the NoctHost TeamMay 13, 20267 min read

A Telegram bot that only runs while your laptop is open is not much of a bot. To respond at 3am it needs to live on something that is always on, restarts itself if it crashes, and comes back after a reboot. A small VPS plus systemd does all three.

This guide takes a minimal Python bot from a token to a hardened systemd service. The same pattern works for Node, Go, or any other language; only the run command changes.

What you'll need

  • A VPS on Ubuntu 22.04/24.04 with root or sudo.
  • A bot token from @BotFather (open Telegram, message @BotFather, send /newbot, follow the prompts).
  • Basic familiarity with the terminal.

Step 1: Create a dedicated user and project

Never run a bot as root. Create an unprivileged user that owns the code.

adduser --system --group --home /opt/telebot telebot
apt update && apt install -y python3 python3-venv python3-pip
cd /opt/telebot

Step 2: Install dependencies in a virtualenv

A virtualenv keeps the bot's packages isolated from the system Python.

python3 -m venv /opt/telebot/venv
/opt/telebot/venv/bin/pip install --upgrade pip
/opt/telebot/venv/bin/pip install python-telegram-bot==21.6

Step 3: Write the bot

Save this as /opt/telebot/bot.py. It reads the token from an environment variable (so the secret never sits in your code) and replies to /start and any text message.

/opt/telebot/bot.py
import os
import logging
from telegram import Update
from telegram.ext import (
    Application, CommandHandler, MessageHandler, ContextTypes, filters,
)

logging.basicConfig(level=logging.INFO)

async def start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text('Bot is alive. Send me anything.')

async def echo(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text(update.message.text)

def main():
    token = os.environ['BOT_TOKEN']
    app = Application.builder().token(token).build()
    app.add_handler(CommandHandler('start', start))
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
    app.run_polling()

if __name__ == '__main__':
    main()
chown -R telebot:telebot /opt/telebot

Step 4: Store the token safely

Put the token in an environment file readable only by root, and point systemd at it.

/etc/telebot.env
BOT_TOKEN=123456:ABC-YourTokenFromBotFather
chmod 600 /etc/telebot.env

Step 5: Create the systemd service

This unit runs the bot as the telebot user, loads the token, and restarts automatically on crash.

/etc/systemd/system/telebot.service
[Unit]
Description=Telegram bot
After=network-online.target
Wants=network-online.target

[Service]
User=telebot
Group=telebot
EnvironmentFile=/etc/telebot.env
WorkingDirectory=/opt/telebot
ExecStart=/opt/telebot/venv/bin/python /opt/telebot/bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now telebot
systemctl status telebot
Tip — Restart=always plus enable is what makes it 24/7: the bot comes back after a crash, an OOM kill, or a full reboot, with no babysitting.

Step 6: Read the logs

All stdout/stderr goes to the journal. Follow it live or grep the last hour.

journalctl -u telebot -f
journalctl -u telebot --since '1 hour ago'

Keep it running and updating

  • Deploy new code: edit bot.py, then systemctl restart telebot.
  • Polling (used here) needs no open inbound ports. If you switch to webhooks, open 443 and put the bot behind a reverse proxy with TLS.
  • Prefer pm2 or screen for Node bots? Those work too, but systemd is the only option that reliably survives a reboot without extra setup.

A polling bot is almost free to host: it sips CPU and RAM, so the smallest box is plenty. With hourly billing you can deploy in about 60 seconds, run the bot for cents while you test, and destroy the server the moment the project is over.

Spin one up in about a minute

Email signup, pay with crypto, hourly billing. Trying a box costs cents — destroy it when you are done.

Deploy a server

Frequently asked

Polling or webhooks?
Polling is simpler and needs no public port or TLS, which makes it ideal for getting started. Webhooks scale better and respond with lower latency, but they require an HTTPS endpoint. Start with polling and switch only if you outgrow it.
Why systemd instead of just nohup or screen?
nohup and screen keep a process alive while you are logged out, but they do not restart it after a crash and do not survive a reboot. systemd gives you auto-restart, boot persistence, and centralized logging in one unit file.

Keep reading