🐍
Hosting

Deploying an aiogram Bot to a Server: virtualenv, systemd and Webhooks

14.11.2026
← All articles

aiogram is one of the most popular Python libraries for Telegram bots: asynchronous, organized around routers and filters, with FSM support for multi-step conversations. On a laptop, python bot.py works perfectly until the lid closes. Here we deploy an aiogram 3 ordering bot for a clothing shop in Namangan to a VPS and turn it into an always-on service.

Why a VPS

A polling bot is a long-running Python process. Shared hosting is built mainly for PHP sites and usually limits or kills long-lived background processes, and restarting a bot from cron is not reliable. A small VPS with systemd is the right home; on sayt.uz you can order one from the cabinet.

Preparing the code

Never hard-code the token; read it from the environment. Enable logging, because on a server logs are your only window into errors.

import asyncio, logging, 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):
    await message.answer("Hello! Type the product you need.")

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

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(main())

Pin versions in requirements.txt, for example aiogram>=3.0,<4.0, since versions 2 and 3 differ a lot.

Virtual environment and secrets

sudo apt install -y python3-venv git
sudo adduser --disabled-password bot
sudo -iu bot
git clone https://github.com/misol/shop-bot.git
cd shop-bot
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Put BOT_TOKEN=... in .env, run chmod 600 .env and keep the file in .gitignore.

A systemd service

# /etc/systemd/system/shop-bot.service
[Unit]
Description=Shop Telegram bot (aiogram)
After=network-online.target
Wants=network-online.target

[Service]
User=bot
WorkingDirectory=/home/bot/shop-bot
EnvironmentFile=/home/bot/shop-bot/.env
ExecStart=/home/bot/shop-bot/.venv/bin/python bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable it with sudo systemctl enable --now shop-bot and watch logs with journalctl -u shop-bot -f. To update: git pull, reinstall requirements if needed, sudo systemctl restart shop-bot. Run only one instance; a forgotten copy on your laptop causes TelegramConflictError.

Keep FSM state across restarts

The default MemoryStorage loses every half-finished order when the service restarts. Use Redis in production, and store the orders themselves in a real database such as PostgreSQL or MySQL.

from aiogram.fsm.storage.redis import RedisStorage

storage = RedisStorage.from_url("redis://127.0.0.1:6379/0")
dp = Dispatcher(storage=storage)

Switching to a webhook

With more users, or when the bot shares a server with a website, a webhook can be the better fit. aiogram ships an aiohttp integration; Nginx terminates HTTPS and forwards to the internal port, and secret_token rejects forged requests.

from aiohttp import web
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application

SECRET = os.environ["WEBHOOK_SECRET"]

async def on_startup(bot: Bot):
    await bot.set_webhook("https://bot.example.uz/tg/webhook", secret_token=SECRET)

def main():
    bot = Bot(token=os.environ["BOT_TOKEN"])
    dp.startup.register(on_startup)
    app = web.Application()
    SimpleRequestHandler(dispatcher=dp, bot=bot, secret_token=SECRET).register(app, path="/tg/webhook")
    setup_application(app, dp, bot=bot)
    web.run_app(app, host="127.0.0.1", port=8081)

Point an Nginx location /tg/webhook at http://127.0.0.1:8081 and get a Let's Encrypt certificate for the subdomain. The systemd unit stays the same; only the entry script changes.

Related articles

🛠️ Site Broke After a PHP Upgrade: How to Recover Fast 🔀 Moving from cPanel to ISPmanager: Files, Databases, Mail and DNS 🖥️ Panel terminal keeps disconnecting: why, and how to connect over SSH properly 🔍 Finding the large files eating your hosting disk space: commands and tips
🌐 Language
🇺🇿 O'zbek 🇺🇿 Ўзбек 🇷🇺 Русский 🇬🇧 English ✓