1. Polling vs Webhook — что выбрать
Есть два способа получать обновления от Telegram. Выбор влияет на сложность деплоя.
| Параметр | Polling | Webhook |
|---|---|---|
| Как работает | Бот спрашивает Telegram: «есть обновления?» | Telegram сам шлёт POST на ваш URL |
| Нужен домен + SSL | ❌ Нет | ✅ Обязательно |
| Задержка доставки | ~1–3 сек | <100 мс |
| Нагрузка на Telegram API | Выше (постоянные запросы) | Минимальная |
| Сложность настройки | Простая | Средняя (Nginx + SSL) |
| Масштабируемость | Один процесс | Несколько воркеров |
| Когда использовать | Большинство ботов | >1000 сообщений/день |
2. Создание бота в BotFather
Если бота ещё нет — создайте его через официального бота Telegram:
/revoke у BotFather.3. Python 3.11 и виртуальное окружение
Никогда не устанавливайте пакеты глобально через pip install без venv. Виртуальное окружение изолирует зависимости бота от системы — разные боты могут использовать разные версии библиотек без конфликтов.
# Обновляем систему
apt update && apt upgrade -y
# Устанавливаем Python 3.11 (в Ubuntu 22.04 есть в базовых репо)
apt install -y python3.11 python3.11-venv python3.11-dev
# Создаём пользователя для бота (не запускаем под root)
useradd -r -m -s /bin/bash botuser
su - botusermkdir -p ~/mybot
cd ~/mybot
# Создаём venv
python3.11 -m venv venv
# Активируем
source venv/bin/activate
# Теперь pip устанавливает только в venv, не в систему
pip install --upgrade pip
pip install aiogram==3.17.0 python-dotenvpip freeze > requirements.txt
# На другом сервере: pip install -r requirements.txt4. Структура проекта
Минимальная структура для production-бота. Хендлеры отделены от точки входа — удобно добавлять новые команды не трогая основной файл:
mybot/
├── venv/ # виртуальное окружение (не коммитить!)
├── bot.py # точка входа
├── config.py # настройки из .env
├── handlers/
│ ├── __init__.py
│ ├── commands.py # /start, /help
│ └── messages.py # ответы на текст
├── .env # токен и секреты (не коммитить!)
├── .env.example # шаблон .env для документации
├── .gitignore
└── requirements.txt.env
.env.local
venv/
__pycache__/
*.pyc
*.pyo
.DS_Store5. Токен в .env — правильно и безопасно
Единственный правильный способ хранить токен — в переменных окружения. Библиотека python-dotenv загружает их из файла .env:
BOT_TOKEN=5123456789:ABCDefghIJKLmnopQRSTuvwxyz12345678
# Дополнительные настройки:
ADMIN_ID=123456789
LOG_LEVEL=INFOBOT_TOKEN=your_bot_token_here
ADMIN_ID=your_telegram_user_id
LOG_LEVEL=INFOfrom dotenv import load_dotenv
import os
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
ADMIN_ID = int(os.getenv("ADMIN_ID", "0"))
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
if not BOT_TOKEN:
raise ValueError("BOT_TOKEN не задан в .env!")6. Код бота на aiogram 3.x
Aiogram 3 использует роутеры — каждый хендлер регистрируется на своём роутере, потом роутеры подключаются к диспетчеру. Это удобно для разбивки кода по файлам.
from aiogram import Router
from aiogram.filters import CommandStart, Command
from aiogram.types import Message
router = Router()
@router.message(CommandStart())
async def cmd_start(message: Message):
await message.answer(
f"Привет, {message.from_user.full_name}! 👋\n"
"Я работаю на VPS 24/7.\n\n"
"Команды:\n"
"/help — помощь"
)
@router.message(Command("help"))
async def cmd_help(message: Message):
await message.answer(
"Список команд:\n"
"/start — запустить бота\n"
"/help — эта справка"
)from aiogram import Router, F
from aiogram.types import Message
router = Router()
@router.message(F.text)
async def echo_message(message: Message):
# Простое эхо — замените на свою логику
await message.answer(f"Вы написали: {message.text}")from .commands import router as commands_router
from .messages import router as messages_router
routers = [commands_router, messages_router]import asyncio
import logging
from aiogram import Bot, Dispatcher
from config import BOT_TOKEN, LOG_LEVEL
from handlers import routers
# Настройка логирования
logging.basicConfig(
level=getattr(logging, LOG_LEVEL),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
async def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# Подключаем все роутеры
for router in routers:
dp.include_router(router)
logger.info("Бот запускается...")
# Запускаем polling
# drop_pending_updates=True — игнорируем накопившиеся сообщения при рестарте
await dp.start_polling(bot, drop_pending_updates=True)
if __name__ == "__main__":
asyncio.run(main())cd ~/mybot
source venv/bin/activate
python bot.py
# INFO: Бот запускается...
# Напишите /start боту в Telegram — должен ответитьЕсли бот ответил — останавливайте Ctrl+C и переходим к автозапуску.
7. Автозапуск через systemd
Systemd запустит бота при старте сервера и перезапустит если он упадёт.EnvironmentFile загружает .env — токен не нужно прописывать в unit-файле.
[Unit]
Description=Telegram Bot (aiogram)
After=network.target
[Service]
Type=simple
User=botuser
WorkingDirectory=/home/botuser/mybot
# Загружаем переменные из .env
EnvironmentFile=/home/botuser/mybot/.env
# Запуск через venv
ExecStart=/home/botuser/mybot/venv/bin/python bot.py
# Перезапуск при любом сбое (кроме Ctrl+C)
Restart=always
RestartSec=5s
# Graceful shutdown: сначала SIGTERM, через 15 сек SIGKILL
KillMode=mixed
TimeoutStopSec=15s
# Логи идут в journald
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload
systemctl enable telegram-bot # автозапуск при загрузке
systemctl start telegram-bot # запустить сейчас
# Проверяем статус
systemctl status telegram-bot# Логи в реальном времени
journalctl -u telegram-bot -f
# Последние 50 строк
journalctl -u telegram-bot -n 50
# Только ошибки
journalctl -u telegram-bot -p err
# Перезапуск после обновления кода
systemctl restart telegram-bot8. Обновление бота без даунтайма
Рабочий процесс: скопировали новый код → перезапустили сервис. Systemd отправит SIGTERM, aiogram корректно завершит текущие обработчики, потом запустит новую версию. Сообщения пользователей не теряются — Telegram буферизует их на своей стороне.
Вариант А: через rsync с локальной машины
#!/bin/bash
# Копируем код (исключая venv, .env и кэш)
rsync -avz \
--exclude='venv/' \
--exclude='.env' \
--exclude='__pycache__/' \
--exclude='*.pyc' \
./mybot/ \
botuser@IP_СЕРВЕРА:/home/botuser/mybot/
# Устанавливаем зависимости и перезапускаем
ssh botuser@IP_СЕРВЕРА "
cd ~/mybot
source venv/bin/activate
pip install -r requirements.txt -q
sudo systemctl restart telegram-bot
echo 'Готово!'
"chmod +x deploy.sh
./deploy.shВариант Б: через git (если репозиторий настроен)
cd ~/mybot
git pull origin main
source venv/bin/activate
pip install -r requirements.txt -q
sudo systemctl restart telegram-bot9. Webhook — для высоконагруженных ботов
Webhook нужен когда бот получает тысячи сообщений в день и важна минимальная задержка. Требует домен + SSL. Используем Nginx как reverse proxy и встроенный сервер aiohttp из aiogram.
Дополнение к .env для webhook
WEBHOOK_HOST=https://yourdomain.ru
WEBHOOK_PATH=/webhook
WEBHOOK_SECRET=минимум_32_случайных_символа_здесь
WEBAPP_PORT=8080bot.py в режиме webhook
import asyncio
import logging
from aiogram import Bot, Dispatcher
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from aiohttp import web
from config import BOT_TOKEN, LOG_LEVEL
from handlers import routers
import os
load_dotenv()
WEBHOOK_HOST = os.getenv("WEBHOOK_HOST")
WEBHOOK_PATH = os.getenv("WEBHOOK_PATH", "/webhook")
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")
WEBAPP_PORT = int(os.getenv("WEBAPP_PORT", "8080"))
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
logging.basicConfig(level=getattr(logging, LOG_LEVEL))
async def on_startup(bot: Bot):
await bot.set_webhook(
url=WEBHOOK_URL,
secret_token=WEBHOOK_SECRET,
drop_pending_updates=True,
)
print(f"Webhook установлен: {WEBHOOK_URL}")
async def on_shutdown(bot: Bot):
await bot.delete_webhook()
def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
for router in routers:
dp.include_router(router)
dp.startup.register(on_startup)
dp.shutdown.register(on_shutdown)
app = web.Application()
SimpleRequestHandler(
dispatcher=dp,
bot=bot,
secret_token=WEBHOOK_SECRET,
).register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)
web.run_app(app, host="127.0.0.1", port=WEBAPP_PORT)
if __name__ == "__main__":
main()Nginx конфиг для webhook
server {
listen 443 ssl;
server_name yourdomain.ru;
ssl_certificate /etc/letsencrypt/live/yourdomain.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.ru/privkey.pem;
location /webhook {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
server_name yourdomain.ru;
return 301 https://$host$request_uri;
}Чеклист готовности
- ✓Токен получен от @BotFather
- ✓Токен в .env, .env добавлен в .gitignore
- ✓venv создан, зависимости в requirements.txt
- ✓python bot.py работает локально — бот отвечает
- ✓systemd сервис создан и включён (systemctl enable)
- ✓Бот отвечает после sudo systemctl start telegram-bot
- ✓Проверен перезапуск после reboot сервера
- ✓journalctl -u telegram-bot не показывает ошибок