All articles
Guides

How to run a Telegram bot on a VPS: Python, aiogram and systemd

In short

To keep a bot online around the clock, run it on a VPS as a systemd service: the code lives in its own directory with a Python virtual environment, the token in a .env file with mode 600, and systemd brings the bot back after a crash or a server reboot. A polling bot needs no domain and no open ports; a small bot fits the $4.00-a-month configuration.

Key takeaways
  • A Telegram bot on a VPS runs as a systemd service with Restart=always: systemd restarts the bot 5 seconds after a crash and starts it again after a server reboot.
  • A Telegram bot using polling needs no domain, TLS certificate or open ports; a webhook requires a domain with HTTPS and port 443 open.
  • The Telegram bot token from @BotFather belongs in a .env file with mode 600, passed to the process via EnvironmentFile in the systemd unit - not in the code and not in git.
  • A small Telegram bot that answers messages and uses a database fits a VPS with 1 vCPU and 2 GB of RAM; at Tihost that configuration starts at $4.00 a month.

What server a bot needs

A bot that answers messages and talks to a database fits the smallest Starter configuration - 1 vCPU and 2 GB of RAM for $4.00 a month. You need more if the bot processes media, holds thousands of concurrent chats, or shares the server with a database and a website. Load is visible in the panel - start small and move to a larger plan at any time.

Before installing, go through the first server setup: the commands below run as the deploy user with sudo, not as root.

Step 1. Install Python and dependencies

The bot lives in /opt/mybot with its own virtual environment, leaving the system Python untouched:

bash
sudo apt update
sudo apt install -y python3-venv git
sudo mkdir -p /opt/mybot
sudo chown deploy: /opt/mybot
cd /opt/mybot
python3 -m venv .venv
.venv/bin/pip install aiogram

Step 2. Add the bot code

If the code is in a repository, git clone it into /opt/mybot and run .venv/bin/pip install -r requirements.txt. For a test, a minimal aiogram 3 bot will do - save it as /opt/mybot/bot.py:

bot.py
import asyncio
import os

from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message

dp = Dispatcher()


@dp.message(CommandStart())
async def start(message: Message) -> None:
    await message.answer("Bot is running on a VPS")


async def main() -> None:
    bot = Bot(os.environ["BOT_TOKEN"])
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Step 3. Keep the token in .env

The @BotFather token must not end up in the code or in git. Put it in a file only the owner can read:

bash
echo 'BOT_TOKEN=123456789:AA...' > /opt/mybot/.env
chmod 600 /opt/mybot/.env

Step 4. Run the bot as a systemd service

systemd starts the bot when the server boots and restarts it 5 seconds after a crash. EnvironmentFile passes the token from .env into the process environment:

bash
sudo tee /etc/systemd/system/mybot.service > /dev/null <<'EOF'
[Unit]
Description=Telegram bot
After=network-online.target
Wants=network-online.target

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

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now mybot

Step 5. Check the status and logs

The first command shows whether the service is running, the second streams the bot's log in real time:

bash
systemctl status mybot
journalctl -u mybot -f

Send /start to the bot. If it stays silent, the log shows why: a wrong token, an import error or an exception in the code.

How to update the bot

bash
cd /opt/mybot
git pull
.venv/bin/pip install -r requirements.txt
sudo systemctl restart mybot

If the bot keeps data in a database or files, take a backup before updating - the options are covered in the VPS backup guide.

Polling or webhook

With polling, the bot asks Telegram for new messages itself. It needs no domain, certificate or open ports, which makes it the best way to start. With a webhook, Telegram pushes updates to your HTTPS address: lower latency and fewer idle requests, but you need a domain with a certificate and port 443 open. Switching to a webhook makes sense once the bot has many users.

No-console option: Tihost Deploy

If you would rather not set up systemd by hand, create a project on your Linux server under «My Projects» and point it at the code: a GitHub repository, uploaded files or an archive, or code pasted as text. Add the bot token under «Environment variables» - they are stored encrypted. Tihost Deploy handles the build, start and restarts. The subscription is $4.00 a month for the whole account, not per project.

Launch a server in 2 minutes

AMD Ryzen 9, NVMe and DDoS protection in Germany, Finland and Poland. Pay with crypto or card.

Order a Server

FAQ

How much does it cost to host a Telegram bot on a VPS?

Servers start at $4.00 a month; paying for 12 months upfront gives 20% off. A polling bot needs no domain.

The bot stops when I close SSH. Why?

A bot started straight from the terminal exits together with the SSH session. Run it as a systemd service - then the bot runs independently of SSH and comes back after a server reboot.

Where do I see the bot's errors?

In the systemd journal: journalctl -u mybot -f streams the bot's output in real time.

Can I run several bots on one VPS?

Yes, one VPS can run several bots. Give each bot its own directory, virtual environment and systemd service file with a unique name.

Does a Telegram bot need a domain?

A Telegram bot using polling needs no domain. A domain with an HTTPS certificate is only needed for a webhook bot.