Host a Telegram bot on a VPS so it runs 24/7
A bot that runs on your laptop dies every time the lid closes. On a VPS it runs around the clock and restarts itself after a crash or a reboot. Below: a minimal Python bot, a systemd service that keeps it alive, and logs you can actually read.
What you need
- A VPS with Ubuntu 24.04 or Debian 12. For a typical bot the smallest plan is more than enough: Pulse has 2 vCPU and 2 GB of RAM for $10/month, and an aiogram bot uses 60–100 MB of it, so there is room for several bots or a small database.
- A bot token from @BotFather.
- SSH access to the server (see Connecting over SSH).
Heavier bots are a different story. If yours converts video with ffmpeg, runs a local ML model or keeps a large database, start at Pulse or Drive.
1. A separate user and a virtual environment
Don't run the bot as root. One extra user costs nothing and limits the damage if the bot's code has a hole.
apt update && apt install -y python3-venv
useradd -r -m -d /opt/bot -s /usr/sbin/nologin bot
sudo -u bot python3 -m venv /opt/bot/venv
sudo -u bot /opt/bot/venv/bin/pip install aiogram
2. The bot
/opt/bot/bot.py: replies to /start and echoes messages back. Replace the handlers with your logic.
import asyncio, os
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
dp = Dispatcher()
@dp.message(CommandStart())
async def start(m: Message):
await m.answer("Hi! I'm running on a VPS.")
@dp.message()
async def echo(m: Message):
await m.answer(m.text or "…")
async def main():
await dp.start_polling(Bot(os.environ["BOT_TOKEN"]))
asyncio.run(main())
Keep the token out of the code, in a file only the bot user can read:
echo 'BOT_TOKEN=123456:ABC...' > /opt/bot/.env
chown bot:bot /opt/bot/.env /opt/bot/bot.py && chmod 600 /opt/bot/.env
3. systemd keeps it running
/etc/systemd/system/bot.service:
[Unit]
Description=Telegram bot
After=network-online.target
Wants=network-online.target
[Service]
User=bot
WorkingDirectory=/opt/bot
EnvironmentFile=/opt/bot/.env
ExecStart=/opt/bot/venv/bin/python bot.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now bot
systemctl status bot
Restart=always brings the bot back five seconds after any crash. enable starts it after a server reboot.
4. Logs and updates
journalctl -u bot -f # live log
journalctl -u bot --since today # today's output
To deploy a new version, copy the file and run systemctl restart bot. If you keep the code in git, a git pull in /opt/bot followed by the restart is all the deployment most bots ever need.
Polling or webhook?
The example uses polling: the bot asks Telegram for updates itself. It needs no domain, no certificate and no open ports, which is why I'd start with it. A webhook makes sense when the bot handles a lot of traffic or already lives next to a web app with HTTPS. Then Telegram pushes updates to your URL, and you'll need a domain and a certificate (nginx and Let's Encrypt do the job).
One honest caveat: polling keeps a long-lived connection to Telegram open. That's normal, and it costs almost nothing in traffic, which is unlimited on every dedisrv VPS anyway.