Telegram-бота на VPS можно запустить двумя основными способами: через polling или через webhook. Для простого проекта, личного бота или небольшого MVP чаще достаточно polling-бота под systemd. Для production-сценария лучше использовать Docker, webhook, HTTPS, reverse proxy и отдельный .env-файл с токенами.
Простая архитектура через polling выглядит так: Telegram API ← polling ← bot process на VPS
Бот сам регулярно обращается к Telegram API и забирает новые сообщения. Для такой схемы не нужен домен, HTTPS и reverse proxy. Главное — запустить бота как сервис, настроить автоперезапуск и логи.
Production-вариант через webhook выглядит иначе: Telegram API → HTTPS webhook → reverse proxy → bot container
В этом случае Telegram сам отправляет обновления на HTTPS-адрес бота. Для webhook нужны домен, SSL-сертификат, reverse proxy и корректный публичный URL.
Минимальная рабочая схема для VPS должна включать:
- Отдельную директорию проекта;
- Зависимости Python или Node.js;
- .env для токена и настроек;
- Запуск через systemd или Docker Compose;
- Автоперезапуск после падения;
- Просмотр логов;
- Понятный деплой после обновления кода;
- Ограничение админских команд;
- Защиту токена от попадания в репозиторий.
Для polling-бота обычно достаточно systemd unit, или даже запуска скрипта по cron. Он запускает процесс после перезагрузки сервера, перезапускает его при ошибке и позволяет смотреть логи через journalctl.
Для Docker-варианта проект обычно содержит Dockerfile, docker-compose.yml, .env и код бота. Контейнер получает токен из env-файла, запускается с restart policy и обновляется через пересборку после git pull.
Главное правило по секретам: токен Telegram-бота нельзя хранить прямо в коде и отправлять в Git. Его лучше держать в .env, который добавлен в .gitignore. Если токен случайно попал в репозиторий, его нужно перевыпустить через BotFather.
Polling и webhook не стоит смешивать без понимания. Если бот работает через polling, webhook обычно должен быть отключён. Если используется webhook, бот не должен параллельно пытаться забирать updates через polling.
После запуска нужно проверить не только ответ бота в Telegram. Важно убедиться, что сервис поднимается после reboot, логи доступны, env-файл читается, токен не лежит в репозитории, restart policy работает, webhook использует валидный HTTPS, а админские команды доступны только нужным пользователям.
Типовые ошибки: запускать бота вручную в SSH-сессии, хранить токен в репозитории, не настраивать автоперезапуск, путать polling и webhook, не проверять SSL для webhook, открывать лишние порты и не ограничивать доступ к админским командам.
Правильный подход такой: для простого бота — polling через systemd; для более серьёзной установки — Docker, webhook, HTTPS, reverse proxy, .env, restart policy, логи и понятный процесс деплоя после обновления кода.
Две архитектуры запуска Telegram-бота

Telegram-бота на VPS можно разместить по-разному. Самые частые варианты — polling через systemd и webhook через Docker, HTTPS и reverse proxy.
Оба подхода рабочие, но решают разные задачи. Polling проще запустить и проще отлаживать. Webhook требует больше настройки, зато лучше подходит для production-сценариев, где важны домен, контейнеризация, HTTPS и управляемый деплой.
Polling через systemd
Polling — это схема, при которой бот сам обращается к Telegram API и забирает новые updates. Ему не нужен публичный HTTPS-адрес, домен или reverse proxy.
Упрощённо это выглядит так: VPS → bot process → Telegram API
Бот запускается как обычное приложение на сервере. Например, Python-бот через python main.py или Node.js-бот через node index.js.
Но запускать его вручную в SSH-сессии нельзя считать нормальным деплоем. Если закрыть терминал, оборвётся соединение или сервер перезагрузится, бот может остановиться.
Как вариант, бота можно запускать через cron, разово при старте, если цикличность опроса есть в коде или на регулярной основе, вынеся цикл на планировщик. Но такая система хуже диагностируется при возможных сбоях.
Поэтому polling-бот обычно запускают через systemd. Unit-файл описывает, от какого пользователя запускать бота, в какой директории, какой командой, с какими env-переменными и что делать при падении процесса.
Базовая логика такая: код бота → systemd service → автозапуск → restart при ошибке → логи через journalctl
Для простого Telegram-бота это часто самый практичный вариант. Он не требует домена, SSL и настройки webhook, но при этом даёт автозапуск, управление сервисом и нормальные логи.
Polling особенно удобен для личных ботов, внутренних утилит, MVP, небольших проектов и сценариев, где не нужна сложная инфраструктура.
Webhook через Docker и HTTPS
Webhook работает иначе. В этом варианте Telegram сам отправляет updates на публичный HTTPS-адрес бота.
Схема выглядит так: Telegram API → https://bot.example.com/webhook → reverse proxy → bot container
Для webhook нужен публичный адрес, доступный из интернета. Обычно это домен или поддомен, например:
bot.example.com
На VPS запрос принимает reverse proxy: Nginx, Caddy, Traefik или другой сервер. Он обрабатывает HTTPS, принимает запрос от Telegram и передаёт его внутрь приложения или контейнера.
В production-варианте бота удобно запускать через Docker Compose. Тогда структура становится более предсказуемой:
- Код бота лежит в проекте;
- Зависимости собираются в Docker image;
- Токены передаются через .env;
- Контейнер запускается с restart policy;
- Логи смотрятся через Docker;
- Деплой выполняется через git pull, rebuild и restart.
Webhook-вариант требует больше подготовки, но лучше подходит, когда бот становится частью рабочей инфраструктуры. Особенно если рядом есть база данных, очередь, API, reverse proxy, другие контейнеры и единый процесс деплоя.
Главное условие — валидный HTTPS. Если сертификат неправильный, домен не резолвится или reverse proxy настроен с ошибкой, Telegram не сможет стабильно отправлять updates на webhook.
Когда выбрать polling
Polling стоит выбрать, если нужен простой и надёжный запуск без лишней инфраструктуры. Это хороший вариант для первого деплоя Telegram-бота на VPS.
Polling подойдёт, если:
- Бот небольшой;
- Проект личный или внутренний;
- Нет домена;
- Не хочется настраивать SSL и безопасность;
- Нет высокой нагрузки;
- Нужна простая отладка;
- Бот можно запускать одним процессом;
- Достаточно автозапуска через systemd.
Такой подход особенно удобен для Python-ботов на aiogram, python-telegram-bot или pyTelegramBotAPI, а также для Node.js-ботов на Telegraf или node-telegram-bot-api.
Главное — не оставлять процесс в SSH-сессии. Даже простой polling-бот должен работать как сервис:
systemctl start bot
systemctl enable bot
journalctl -u bot -f
Polling не требует входящих соединений от Telegram, поэтому firewall может быть проще. Обычно достаточно SSH-доступа для администратора и исходящего доступа к Telegram API.
Но polling не всегда удобен для более сложных production-сценариев. Если проект уже использует Docker, домен, HTTPS и reverse proxy, логичнее рассмотреть webhook.
Когда выбрать webhook
Webhook лучше выбирать, когда бот должен быть частью более аккуратной production-архитектуры. Это особенно полезно, если проект уже разворачивается через Docker Compose и работает за reverse proxy.
Webhook подойдёт, если:
- Есть домен и HTTPS;
- Используется Docker;
- Нужен единый production-деплой;
- Бот работает вместе с API, базой или другими контейнерами;
- Важна управляемая сетевая схема;
- Нужно принимать updates через публичный endpoint;
- Проект рассчитан на команду или бизнес;
- Есть мониторинг, логи и регулярные обновления.
Webhook требует больше дисциплины. Нужно проверить DNS, SSL, reverse proxy, путь webhook, переменные окружения, открытые порты и доступность endpoint из интернета.
Также важно не путать режимы. Если бот работает через webhook, не нужно параллельно запускать polling. И наоборот: если используется polling, старый webhook лучше удалить, чтобы не было конфликтов и потерянных updates.
Для небольшого бота polling через systemd обычно быстрее и проще. Для production-сервиса с доменом, HTTPS, Docker и предсказуемым деплоем лучше подходит webhook.
Подготовка VPS

Перед запуском Telegram-бота нужно подготовить сервер: обновить систему, установить нужный runtime, создать отдельного пользователя, разложить проект по понятной структуре и вынести токены в .env.
Эти шаги нужны для обоих вариантов: и для простого polling-бота через systemd, и для production-схемы через Docker, webhook и HTTPS.
Обновление системы
Начать стоит с обновления пакетов. Для Ubuntu или Debian:
sudo apt update
sudo apt upgrade -y
После этого полезно установить базовые утилиты: sudo apt install -y git curl nano ufw
Если бот будет запускаться через systemd, на этом этапе также важно проверить, что сервер нормально перезагружается и сервисы стартуют автоматически.
Для базовой защиты стоит включить firewall и оставить открытым SSH. Если дальше будет использоваться webhook, понадобятся ещё порты 80 и 443 для HTTP/HTTPS.
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
Для polling-бота порты 80 и 443 не обязательны, потому что Telegram не отправляет запросы на сервер. Бот сам обращается к Telegram API. Но если на этом же VPS будет сайт, reverse proxy или webhook, эти порты понадобятся.
После обновления системы можно ставить Python или Node.js — в зависимости от того, на чём написан бот.
Установка Python или Node.js
Для Python-бота обычно нужны Python, pip и virtual environment.
sudo apt install -y python3 python3-pip python3-venv
Проверить версии:
python3 --version
pip3 --version
Для Node.js-бота лучше использовать актуальную LTS-версию Node.js. Один из вариантов — установка через NodeSource или другой официальный способ, подходящий для вашей системы.
После установки проверьте:
node -v
npm -v
Если бот будет запускаться через Docker, устанавливать Python или Node.js прямо на VPS не обязательно. Runtime будет внутри контейнера. На сервере тогда нужны Docker и Docker Compose.
Но для простого systemd-варианта runtime должен быть установлен на самом VPS, потому что сервис будет запускать Python- или Node.js-процесс напрямую.
Создание пользователя для бота
Не стоит запускать бота от root. Лучше создать отдельного системного пользователя, например botuser.
sudo adduser --system --group --home /opt/telegram-bot botuser
Так проще ограничить права проекта и отделить бота от системных файлов. Если в коде появится ошибка, она не должна давать приложению лишний доступ ко всему серверу.
Директорию проекта можно разместить в /opt:
sudo mkdir -p /opt/telegram-bot
sudo chown -R botuser:botuser /opt/telegram-bot
Дальше код можно клонировать из репозитория или загрузить вручную:
cd /opt/telegram-bot
sudo -u botuser git clone https://github.com/example/telegram-bot.git .
Если репозиторий приватный, доступ лучше настраивать через SSH-ключи или deploy token, а не через пароль в командной строке.
Отдельный пользователь особенно важен для systemd: в unit-файле можно явно указать, от кого запускать процесс.
User=botuser
Group=botuser
WorkingDirectory=/opt/telegram-bot
После пользователя и прав нужно привести проект к понятной структуре.
Структура проекта
Структура проекта должна быть простой: отдельная директория для кода, отдельный файл зависимостей, отдельный .env для токенов и понятная точка входа. Тогда бота проще запускать, обновлять и переносить на другой сервер.
Для Python-бота минимальная структура может быть такой:
| Путь или файл | Для чего нужен |
| /opt/telegram-bot/ | Корневая директория проекта на VPS |
| main.py | Точка входа: запуск бота |
| bot/ | Основной код: обработчики, конфиг, вспомогательные модули |
| requirements.txt | Python-зависимости |
| .env | Токен бота, ID администраторов и настройки окружения |
| README.md | Краткое описание запуска и деплоя |
Для Node.js-бота структура похожая, но вместо requirements.txt используются файлы npm:
| Путь или файл | Для чего нужен |
| /opt/telegram-bot/ | Корневая директория проекта на VPS |
| index.js | Точка входа: запуск бота |
| src/ | Основной код: обработчики, конфиг, модули |
| package.json | Зависимости и команды запуска |
| package-lock.json | Зафиксированные версии пакетов |
| .env | Токен бота, ID администраторов и настройки окружения |
| README.md | Краткое описание запуска и деплоя |
Для Docker-варианта к проекту обычно добавляют ещё несколько файлов:
- Dockerfile — инструкция сборки контейнера;
- docker-compose.yml — описание запуска контейнера или контейнеров;
- .dockerignore — файлы, которые не нужно отправлять в Docker build.
Важно не хранить токены, временные файлы и локальные зависимости в репозитории. Для этого в .gitignore обычно добавляют:
.env
*.log
__pycache__/
node_modules/
.venv/
Так проект остаётся чистым: код лежит в Git, секреты хранятся в .env, зависимости устанавливаются отдельно, а запуск через systemd или Docker использует одну понятную директорию.
Env-файл для токенов и настроек
Токен Telegram-бота нельзя хранить прямо в коде. Его нужно вынести в .env и не добавлять этот файл в репозиторий.
Пример .env:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
ENV=production
LOG_LEVEL=info
Для webhook-варианта могут понадобиться дополнительные значения:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
WEBHOOK_URL=https://bot.example.com/webhook
WEBHOOK_SECRET=change_this_secret
PORT=3000
BOT_TOKEN выдаётся через BotFather. Если токен случайно попал в публичный репозиторий, его нужно перевыпустить, а старый считать скомпрометированным.
ADMIN_IDS лучше хранить явно. Так админские команды можно ограничить по Telegram user ID, а не по username, который пользователь может поменять.
В Python .env часто читают через python-dotenv, в Node.js — через dotenv. Но сам принцип одинаковый: код берёт секреты из окружения, а не содержит их внутри файлов проекта.
Права на .env лучше ограничить:
sudo chown botuser:botuser /opt/telegram-bot/.env
sudo chmod 600 /opt/telegram-bot/.env
Для systemd env-файл можно подключить в unit-файле: EnvironmentFile=/opt/telegram-bot/.env
Для Docker Compose env-файл обычно лежит рядом с docker-compose.yml и подставляется в контейнер как переменные окружения.
В итоге подготовка VPS должна привести к понятной базе: система обновлена, runtime установлен, бот не запускается от root, проект лежит в отдельной директории, токены вынесены в .env, а секреты не попадают в Git.
Вариант 1. Polling-бот через systemd

Polling — самый простой способ запустить Telegram-бота на VPS. В этой схеме бот сам обращается к Telegram API, получает новые updates и обрабатывает сообщения.
Для polling не нужны домен, HTTPS и reverse proxy. Но бот всё равно нельзя запускать вручную в SSH-сессии. Нормальный вариант — оформить его как systemd-сервис: с автозапуском, restart policy, env-файлом и логами.
Структура проекта
Для polling-варианта структура проекта должна быть минимальной. Главное — чтобы была понятная точка входа, файл зависимостей и .env с токеном.
Для Python-бота:
| Файл или папка | Назначение |
| /opt/telegram-bot/ | Директория проекта на VPS |
| main.py | Точка входа |
| bot/ | Код бота: handlers, config, utils |
| requirements.txt | Python-зависимости |
| .env | Токен, admin ID и настройки |
Для Node.js-бота:
| Файл или папка | Назначение |
| /opt/telegram-bot/ | Директория проекта на VPS |
| index.js | Точка входа |
| src/ | Код бота: handlers, config, utils |
| package.json | Зависимости и команды запуска |
| .env | Токен, admin ID и настройки |
Токен бота не должен лежать в main.py, index.js или другом файле кода. Его лучше хранить в .env:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
ENV=production
Файл .env нужно добавить в .gitignore, чтобы случайно не отправить токен в репозиторий.
Установка зависимостей
Для Python-бота удобно использовать virtual environment. Команды выполняются из директории проекта:
cd /opt/telegram-bot
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
Если используется aiogram, pyTelegramBotAPI или python-telegram-bot, они должны быть указаны в requirements.txt.
Пример:
aiogram
python-dotenv
Для Node.js-бота зависимости ставятся через npm:
cd /opt/telegram-bot
npm install
В package.json желательно сразу добавить команду запуска:
{
"scripts": {
"start": "node index.js"
}
}
Тогда локальная проверка и запуск через сервис будут понятнее.
Запуск вручную для проверки
Перед настройкой systemd бота нужно один раз запустить вручную и убедиться, что он вообще работает.
Для Python:
cd /opt/telegram-bot
source .venv/bin/activate
python main.py
Для Node.js:
cd /opt/telegram-bot
npm start
На этом этапе нужно проверить:
- Бот запускается без ошибок;
- .env читается;
- Токен корректный;
- Бот отвечает в Telegram;
- Админские команды доступны только нужным ID;
- polling не конфликтует со старым webhook.
Если раньше для этого бота уже был настроен webhook, его лучше удалить перед запуском polling: https://api.telegram.org/bot<ТОКЕН>/deleteWebhook
После успешной проверки процесс можно остановить через Ctrl+C и оформить запуск через systemd.
systemd unit
systemd unit описывает, как запускать бота как сервис. Для Python-бота unit-файл может выглядеть так:
[Unit]
Description=Telegram Bot
After=network.target
[Service]
Type=simple
User=botuser
Group=botuser
WorkingDirectory=/opt/telegram-bot
EnvironmentFile=/opt/telegram-bot/.env
ExecStart=/opt/telegram-bot/.venv/bin/python /opt/telegram-bot/main.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Файл можно сохранить здесь: /etc/systemd/system/telegram-bot.service
Для Node.js-бота ExecStart будет другим: ExecStart=/usr/bin/npm start
или напрямую: ExecStart=/usr/bin/node /opt/telegram-bot/index.js
После создания unit-файла нужно перечитать конфигурацию systemd, включить автозапуск и запустить сервис:
sudo systemctl daemon-reload
sudo systemctl enable telegram-bot
sudo systemctl start telegram-bot
Проверить статус: sudo systemctl status telegram-bot
Если сервис не стартует, обычно причина в неправильном пути к Python/Node.js, правах пользователя, ошибке в .env, отсутствующих зависимостях или неверной рабочей директории.
Автоперезапуск и логи
Главное преимущество systemd — бот не зависит от SSH-сессии. Если закрыть терминал, процесс продолжит работать. Если VPS перезагрузится, сервис поднимется автоматически. Если бот упадёт, systemd попробует перезапустить его.
За это отвечает блок:
Restart=always
RestartSec=5
Логи можно смотреть через journalctl: sudo journalctl -u telegram-bot -f
Последние строки логов: sudo journalctl -u telegram-bot -n 100 --no-pager
Перезапуск после изменения кода или .env: sudo systemctl restart telegram-bot
Остановка: sudo systemctl stop telegram-bot
Если бот обновляется через Git, базовый деплой для polling-варианта может выглядеть так:
cd /opt/telegram-bot
git pull
source .venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart telegram-bot
sudo journalctl -u telegram-bot -n 100 --no-pager
Для Node.js:
cd /opt/telegram-bot
git pull
npm install
sudo systemctl restart telegram-bot
sudo journalctl -u telegram-bot -n 100 --no-pager
После деплоя нужно проверить не только статус сервиса, но и реальный ответ бота в Telegram. active (running) означает, что процесс запущен, но не гарантирует, что токен, polling, обработчики и админские команды работают правильно.
В итоге polling через systemd — хороший вариант для простых Telegram-ботов: минимум инфраструктуры, понятный запуск, автоперезапуск, логи и простой деплой после обновления кода.
Вариант 2. Production через Docker, webhook и HTTPS

Production-вариант отличается от простого polling-запуска тем, что бот принимает обновления через webhook. Telegram отправляет запросы на публичный HTTPS-адрес, reverse proxy принимает их и передаёт в контейнер с ботом.
Базовая схема выглядит так: Telegram API → HTTPS webhook → Nginx → bot container
Такой подход требует больше настройки, чем polling, но лучше подходит для проектов, где уже используются Docker, домен, HTTPS, reverse proxy и понятный процесс деплоя.
Dockerfile и docker-compose.yml
Для Docker-варианта в проект добавляют Dockerfile, docker-compose.yml, .env и .dockerignore. Код остаётся в репозитории, а токены и секреты хранятся отдельно.
Пример Dockerfile для Node.js-бота:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "index.js"]
Пример docker-compose.yml:
services:
telegram-bot:
build: .
container_name: telegram-bot
restart: unless-stopped
env_file:
- .env
ports:
- "127.0.0.1:3000:3000"
В этой схеме контейнер слушает локальный порт 3000, но наружу он не открыт. Внешние запросы должен принимать reverse proxy.
Для Python-бота Dockerfile будет другим:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
Внутри приложения должен быть HTTP endpoint для webhook. Например: POST /webhook
Именно на этот путь Telegram будет отправлять updates.
Reverse proxy и домен
Для webhook нужен домен или поддомен, который указывает на VPS. Например: bot.example.com
DNS-запись должна вести на IP сервера: bot.example.com A 203.0.113.10
Nginx тоже можно добавить в compose-файл, но мы рассмотрим упрощённый вариант с установленным пакетом в операционную систему.
Nginx принимает запросы на домен и передаёт их в контейнер:
server {
listen 80;
server_name bot.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
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 $scheme;
}
}
Такой конфиг можно сохранить в: /etc/nginx/sites-available/bot.example.com
Затем включить сайт и проверить Nginx:
sudo ln -s /etc/nginx/sites-available/bot.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Если бот слушает 127.0.0.1:3000, а Nginx работает на том же VPS, прямой доступ к контейнеру из интернета не нужен. Снаружи должны быть открыты только 80 и 443 порты.
SSL для webhook
Telegram webhook должен быть доступен по HTTPS. Если сертификат невалидный, домен не совпадает или reverse proxy настроен неправильно, Telegram не сможет стабильно отправлять updates. Официальная документация Telegram также описывает работу webhook через HTTPS и отдельный вариант с загрузкой self-signed certificate в setWebhook.
Обычно для VPS используют Let’s Encrypt: sudo certbot --nginx -d bot.example.com
После выпуска сертификата нужно проверить, что домен открывается по HTTPS: curl -I https://bot.example.com
Также стоит проверить конкретный webhook endpoint: curl -I https://bot.example.com/webhook
Метод GET может возвращать 404 или 405, если приложение ждёт только POST. Это не всегда ошибка. Важно, чтобы HTTPS работал, сертификат был валидным, а Nginx передавал запросы в контейнер.
Если используется CDN или внешний proxy, нужно дополнительно проверить SSL-режим. Ошибка между CDN и origin может привести к тому, что в браузере HTTPS работает, а Telegram webhook — нет.
Настройка webhook в Telegram
После того как домен, HTTPS и reverse proxy готовы, нужно зарегистрировать webhook в Telegram.
Базовый запрос:
curl -X POST "https://api.telegram.org/bot$BOT_TOKEN/setWebhook" \
-d "url=https://bot.example.com/webhook"
Если в приложении используется секретный путь или token в URL, webhook может выглядеть так: https://bot.example.com/webhook/secret-path
Дополнительно можно использовать секретный заголовок, если библиотека и приложение его проверяют. Тогда входящие запросы проще отличать от случайных обращений к endpoint.
После настройки webhook стоит проверить статус: curl "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"
В ответе нужно смотреть URL webhook, последние ошибки и количество ожидающих updates.
Если бот раньше работал через polling, важно не запускать polling параллельно с webhook. Эти режимы не нужно смешивать: либо бот сам забирает updates, либо Telegram отправляет их на webhook.
Restart policy и логи контейнера
Контейнер должен автоматически подниматься после перезагрузки VPS и перезапускаться при падении процесса. В Docker Compose за это отвечает restart policy: restart: unless-stopped
Docker описывает unless-stopped как режим, похожий на always, но без автоматического рестарта после ручной остановки контейнера. Для сервисов на VPS это часто удобный вариант.
Запуск контейнера:
cd /opt/telegram-bot
docker compose up -d --build
Проверка состояния: docker compose ps
Логи контейнера: docker compose logs -f telegram-bot
Последние строки логов: docker compose logs telegram-bot --tail=100
После обновления кода production-деплой обычно выглядит так:
cd /opt/telegram-bot
git pull
docker compose up -d --build
docker compose logs telegram-bot --tail=100
После деплоя нужно проверить не только контейнер, но и сам webhook:
- Контейнер запущен;
- HTTPS работает;
- getWebhookInfo не показывает ошибок;
- Бот отвечает в Telegram;
- webhook endpoint получает POST-запросы;
- В логах нет ошибок токена, маршрута или env-файла.
В итоге production-схема через Docker, webhook и HTTPS лучше подходит для ботов, которые должны работать стабильно, обновляться предсказуемо и быть частью нормальной серверной инфраструктуры.
Деплой после обновления кода

После первого запуска бота важно продумать не только установку, но и обычный деплой. Код будет меняться: появятся новые команды, исправления, обработчики, зависимости и настройки.
Хороший деплой должен быть повторяемым. Администратор заходит на VPS, обновляет проект через Git, устанавливает новые зависимости, перезапускает сервис или пересобирает контейнер и проверяет логи.
Обновление проекта через Git
Если код хранится в Git, базовый деплой начинается с перехода в директорию проекта и получения свежей версии.
cd /opt/telegram-bot
git pull
Перед этим стоит убедиться, что на сервере нет незакоммиченных локальных изменений: git status
Если в проекте изменились зависимости, их нужно обновить отдельно. Для Python:
source .venv/bin/activate
pip install -r requirements.txt
Для Node.js: npm install
Файл .env обычно не обновляют через Git. Он должен оставаться локальным на VPS и не попадать в репозиторий. Если появились новые переменные окружения, их добавляют вручную и после этого перезапускают бота.
После обновления кода порядок зависит от того, как бот запущен: через systemd или через Docker Compose.
Перезапуск systemd-сервиса
Если бот работает через polling и systemd, после обновления кода нужно перезапустить сервис.
sudo systemctl restart telegram-bot
Проверить состояние: sudo systemctl status telegram-bot
Если сервис не стартует, нужно смотреть логи: sudo journalctl -u telegram-bot -n 100 --no-pager
Для live-просмотра логов: sudo journalctl -u telegram-bot -f
Обычный деплой для Python-бота через systemd может выглядеть так:
cd /opt/telegram-bot
git pull
source .venv/bin/activate
pip install -r requirements.txt
sudo systemctl restart telegram-bot
sudo journalctl -u telegram-bot -n 100 --no-pager
Для Node.js-бота:
cd /opt/telegram-bot
git pull
npm install
sudo systemctl restart telegram-bot
sudo journalctl -u telegram-bot -n 100 --no-pager
После перезапуска нужно отправить тестовую команду боту в Telegram. Статус active (running) означает, что процесс запущен, но не гарантирует, что обработчики, токен и админские команды работают правильно.
Пересборка Docker-контейнера
Если бот работает через Docker Compose, после обновления кода контейнер нужно пересобрать и перезапустить.
Базовый вариант:
cd /opt/telegram-bot
git pull
docker compose up -d --build
Если в проекте изменился Dockerfile, зависимости или код, --build гарантирует, что образ будет пересобран.
Проверить состояние контейнера: docker compose ps
Посмотреть последние логи: docker compose logs telegram-bot --tail=100
Для live-просмотра: docker compose logs -f telegram-bot
Если используются несколько сервисов, например бот и база данных, можно пересобрать только сервис бота: docker compose up -d --build telegram-bot
Файл .env при таком деплое тоже не должен приходить из Git. Он остаётся на сервере рядом с docker-compose.yml. Если добавились новые переменные, их нужно внести вручную и затем перезапустить контейнер.
После пересборки важно проверить не только контейнер, но и webhook, если бот работает через него.
Проверка логов после деплоя
Логи — обязательная часть деплоя. Без них можно не заметить, что бот запустился с ошибкой, не прочитал .env, не подключился к базе или не принимает updates.
Для systemd: sudo journalctl -u telegram-bot -n 100 --no-pager
Для Docker: docker compose logs telegram-bot --tail=100
После деплоя нужно проверить:
- Процесс или контейнер запущен;
- Нет ошибок импорта модулей;
- Токен прочитан из .env;
- Бот отвечает в Telegram;
- polling не конфликтует с webhook;
- webhook URL доступен по HTTPS;
- Админские команды доступны только нужным пользователям;
- Новые команды или исправления реально работают.
Для webhook-бота дополнительно проверьте состояние webhook в Telegram: curl "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"
Если в ответе есть last_error_message, нужно смотреть Nginx, SSL, маршрут webhook и логи контейнера.
Хороший деплой заканчивается не командой restart, а проверкой результата: бот отвечает, ошибок в логах нет, webhook работает, а новые изменения действительно применились.
Безопасность Telegram-бота на VPS

Telegram-бот на VPS обычно кажется небольшим сервисом, но у него есть доступ к токену, командам, сообщениям пользователей, базе данных, внешним API и иногда к админским функциям. Поэтому безопасность нужно закладывать сразу, а не после первого инцидента.
Минимальная база: токен хранится в .env, секреты не попадают в Git, админские команды проверяют Telegram user ID, firewall не открывает лишние порты, а бот запускается от отдельного пользователя с ограниченными правами.
Где хранить токен
Токен Telegram-бота не должен быть прописан прямо в коде. Плохой вариант: BOT_TOKEN = "123456789:AAExampleToken"
Или для Node.js: const BOT_TOKEN = "123456789:AAExampleToken";
Правильнее хранить токен в .env:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
ENV=production
Код должен читать токен из переменных окружения. Для Python часто используют python-dotenv, для Node.js — dotenv.
Пример для Python:
import os
from dotenv import load_dotenv
load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
Пример для Node.js:
require("dotenv").config();
const BOT_TOKEN = process.env.BOT_TOKEN;
Если бот запускается через systemd, env-файл можно подключить в unit-файле: EnvironmentFile=/opt/telegram-bot/.env
Если бот запускается через Docker Compose, env-файл подключают в docker-compose.yml:
env_file:
- .env
Токен нужно считать секретом. Если он попал в публичный репозиторий, чат, лог или скриншот, его лучше сразу перевыпустить через BotFather.
Как не раскрыть секреты в репозитории
Главное правило: .env не должен попадать в Git. Для этого его добавляют в .gitignore.
.env
*.log
__pycache__/
node_modules/
.venv/
Для Docker также стоит добавить .env в .dockerignore, чтобы токены не попали в build context:
.env
.git
node_modules
.venv
__pycache__
*.log
В репозитории можно оставить пример файла без реальных секретов:
BOT_TOKEN=your_bot_token_here
ADMIN_IDS=123456789
ENV=production
Такой файл обычно называют: .env.example
Он показывает, какие переменные нужны проекту, но не раскрывает реальные токены.
Также не стоит писать токены в README, Dockerfile, package.json, systemd unit, скрипты деплоя или команды в истории shell. Чем меньше мест знает секрет, тем проще его контролировать.
Если секрет уже был закоммичен, недостаточно просто удалить строку в новом коммите. Токен мог остаться в истории Git. В таком случае безопаснее перевыпустить токен у BotFather и заменить его на сервере.
Ограничение админских команд
Если у бота есть админские команды, их нельзя защищать только “скрытым названием”. Команду можно угадать, переслать или найти в коде.
Нужно проверять Telegram user ID отправителя. Username для этого хуже подходит, потому что его можно изменить.
Пример логики:
если user_id есть в ADMIN_IDS → выполнить команду
иначе → отказать
Для Python это может выглядеть так:
ADMIN_IDS = {123456789, 987654321}
if message.from_user.id not in ADMIN_IDS:
return
Для Node.js:
const adminIds = [123456789, 987654321];
if (!adminIds.includes(ctx.from.id)) {
return;
}
Через такую проверку стоит закрывать всё, что влияет на работу бота:
- Рассылки;
- Просмотр заявок;
- Выгрузку данных;
- Управление пользователями;
- Перезапуск сценариев;
- Изменение настроек;
- Доступ к служебной информации;
- Команды для работы с базой.
Если бот работает в группах, нужно дополнительно учитывать chat ID. Пользователь может быть администратором бота, но это не значит, что любую админскую команду можно выполнять в любом чате.
Firewall и открытые порты
Для polling-бота входящие HTTP-порты обычно не нужны. Бот сам обращается к Telegram API, поэтому наружу достаточно оставить SSH для администрирования.
Минимально:
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
Для webhook-варианта нужны 80 и 443 порты, потому что Telegram будет отправлять updates на HTTPS-адрес бота.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Порт приложения, например 3000, лучше не открывать напрямую в интернет. Его можно слушать только на 127.0.0.1, а наружу отдавать через Nginx:
ports:
- "127.0.0.1:3000:3000"
Так внешние запросы идут через HTTPS и reverse proxy, а не напрямую в приложение.
Также не стоит без необходимости открывать порты базы данных, Redis, админ-панелей и внутренних сервисов. Чем меньше открытых портов, тем меньше поверхность атаки.
В качестве усиления защиты можно ограничить входящий трафик на HTTP\HTTPS с адресов Telegram и для метода HTTP-01 LE (для автоматического получения или продления сертификата). Адреса Телеграма публикуются на их сайте, а LE использует для проверки сертификата ендпоинт вида http://bot.example.com/.well-known/acme-challenge. И если ограничения для Telegram достаточно просто прописать в фаерволе, и закрыть https (443) для всего остального, то для сертификата надо будет настраивать реверс-прокси и location-ы. Задача может быть не совсем тривиальной, в качестве примера конфигурации:
server {
listen 80;
server_name bot.example.com wwwbot.example.com;
#пропускаем только трафик HTTP-01
location ^~ /.well-known/acme-challenge/ {
allow all;
root /var/www/certbot; # Must match your ACME client's webroot
default_type "text/plain";
}
# Запрещаем всё остальное на 80 порту
location / {
deny all;
}
}
Также, помимо сетевого доступа, важный момент - это принцип минимальных прав, под которыми работает приложение.
Права пользователя и доступ к файлам
Бота не стоит запускать от root. Лучше создать отдельного пользователя, например botuser, и выдать ему права только на директорию проекта.
sudo adduser --system --group --home /opt/telegram-bot botuser
sudo chown -R botuser:botuser /opt/telegram-bot
Файл .env должен быть доступен только пользователю бота и администратору сервера:
sudo chown botuser:botuser /opt/telegram-bot/.env
sudo chmod 600 /opt/telegram-bot/.env
Для systemd в unit-файле нужно явно указать пользователя:
User=botuser
Group=botuser
WorkingDirectory=/opt/telegram-bot
Так бот не получает лишних прав на системные директории. Если в коде будет ошибка или уязвимость, процесс не должен иметь возможность менять файлы за пределами своего проекта.
Для Docker-варианта тоже важно не монтировать в контейнер лишние директории хоста. Если контейнеру нужен только код и env-переменные, не стоит давать ему доступ ко всему /var, /root или домашним каталогам.
В итоге безопасность Telegram-бота на VPS держится на простых правилах: токен лежит в .env, .env не попадает в Git, админские команды проверяют user ID, наружу открыты только нужные порты, а сам бот работает от отдельного пользователя с ограниченными правами.
Типовые ошибки

Ошибки при размещении Telegram-бота на VPS часто связаны не с кодом, а с эксплуатацией. Бот работает локально, отвечает в Telegram, разработчик быстро переносит его на сервер — и оставляет в состоянии “как-нибудь крутится”.
Для теста это может быть терпимо. Для рабочего бота — нет. Токен должен быть защищён, процесс должен переживать перезагрузку VPS, режим получения updates должен быть выбран осознанно, а админские команды должны быть доступны только нужным пользователям.
Хранить токен в репозитории
Токен Telegram-бота нельзя хранить в коде и отправлять в Git. Если репозиторий публичный, токен может быстро попасть к посторонним. Если репозиторий приватный, риск всё равно остаётся: доступ могут получить подрядчики, бывшие сотрудники, CI/CD, сторонние сервисы или случайные участники проекта.
Плохой вариант: const bot = new Telegraf("123456789:AAExampleToken");
Или так: bot = Bot(token="123456789:AAExampleToken")
Правильнее хранить токен в .env:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
А в коде читать его из переменных окружения.
Если токен уже попал в репозиторий, его нужно считать скомпрометированным. Простого удаления из файла недостаточно: секрет мог остаться в истории Git. Безопаснее перевыпустить токен через BotFather и заменить его на VPS.
Запускать бота вручную в SSH-сессии
Частая ошибка — зайти на VPS по SSH и запустить бота вручную: python main.py
Или: node index.js
Пока терминал открыт, бот работает. Но если SSH-сессия оборвётся, сервер перезагрузится или процесс упадёт, бот может остановиться.
Для рабочего запуска нужен менеджер процесса. В простом polling-варианте это systemd:
sudo systemctl start telegram-bot
sudo systemctl enable telegram-bot
Для Docker-варианта — Docker Compose с restart policy: docker compose up -d
Ручной запуск полезен только для первичной проверки. После этого бот должен работать как сервис или контейнер, а не как процесс, привязанный к открытому терминалу.
Не настраивать restart policy
Бот может упасть из-за ошибки в коде, сетевого сбоя, временной недоступности API, нехватки памяти или неожиданного исключения. Если не настроить перезапуск, он просто остановится.
Для systemd нужен restart в unit-файле:
Restart=always
RestartSec=5
Для Docker Compose: restart: unless-stopped
Это не заменяет исправление ошибок в коде, но помогает переживать временные сбои. Без restart policy бот может молча перестать отвечать, а владелец узнает об этом только от пользователей.
После настройки автоперезапуска всё равно нужно смотреть логи. Если бот падает каждые несколько секунд и постоянно перезапускается, проблема не решена — она просто зациклена.
Путать polling и webhook
Polling и webhook — это два разных режима получения updates от Telegram. В polling бот сам забирает updates. В webhook Telegram отправляет updates на HTTPS-адрес бота.
Проблемы начинаются, когда эти режимы смешивают без понимания. Например, бот запущен через polling, но старый webhook всё ещё активен. Или наоборот: webhook настроен, но код параллельно запускает polling.
Для polling перед запуском лучше удалить webhook: https://api.telegram.org/bot<ТОКЕН>/deleteWebhook
Для webhook нужно настроить публичный HTTPS endpoint: https://bot.example.com/webhook
И зарегистрировать его через setWebhook.
Правило простое: для одного бота в рабочей схеме должен быть выбран один основной режим. Либо polling через systemd, либо webhook через HTTPS и reverse proxy.
Не проверять SSL для webhook
Webhook должен быть доступен по HTTPS. Если SSL-сертификат невалидный, истёк, выпущен не на тот домен или reverse proxy настроен неправильно, Telegram не сможет стабильно отправлять updates.
Проверить домен можно так: curl -I https://bot.example.com
Проверить webhook endpoint: curl -I https://bot.example.com/webhook
Если endpoint принимает только POST, ответ на GET может быть 404 или 405. Это не всегда проблема. Важно, чтобы HTTPS-соединение устанавливалось, сертификат был валидным, а запрос доходил до приложения.
Также стоит проверять состояние webhook через Telegram API: curl "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"
Если там есть last_error_message, нужно смотреть SSL, DNS, Nginx, маршрут webhook и логи контейнера.
Не ограничивать доступ к админским командам
Админские команды нельзя оставлять доступными всем пользователям. Даже если команда “скрытая”, её можно угадать, переслать, найти в коде или случайно вызвать.
Опасные команды нужно закрывать проверкой Telegram user ID:
если user_id есть в ADMIN_IDS → выполнить команду
иначе → отказать
Ограничивать нужно всё, что влияет на работу бота или данные:
- Рассылки;
- Выгрузки;
- Просмотр заявок;
- Управление пользователями;
- Изменение настроек;
- Служебные команды;
- Доступ к статистике;
- Операции с базой данных.
Username для такой проверки использовать нежелательно: его можно поменять. Надёжнее хранить числовые Telegram user ID в .env.
Если бот работает в группах, стоит проверять не только пользователя, но и chat ID. Администратор может иметь право на команду в личном чате с ботом, но это не значит, что команда должна выполняться в любой группе.
В итоге большинство ошибок при размещении Telegram-бота сводятся к одному: бот запускают как временный скрипт, а используют как рабочий сервис. Для VPS нужна нормальная схема: токен в .env, запуск через systemd или Docker, restart policy, понятный режим polling/webhook, проверенный HTTPS и закрытые админские команды.
Заключение

Для Telegram-бота на VPS есть два практичных варианта. Простой polling-бот удобно запускать через systemd: это не требует домена, HTTPS и reverse proxy, но даёт автозапуск, restart policy и нормальные логи.
Production-схема через Docker, webhook и HTTPS требует больше настройки, зато лучше подходит для рабочих проектов. В ней бот работает в контейнере, получает updates через защищённый endpoint, стоит за reverse proxy и обновляется через понятный деплой.
В обоих вариантах важны одни и те же базовые правила: токен хранится в .env, секреты не попадают в Git, бот не запускается вручную в SSH-сессии, restart policy включён, логи проверяются после деплоя, а админские команды ограничены по Telegram user ID.
FAQ
Что лучше выбрать для Telegram-бота: polling или webhook?
Для простого бота чаще удобнее polling. Он не требует домена, SSL и reverse proxy: бот сам обращается к Telegram API и забирает updates. Такой вариант хорошо подходит для личных проектов, MVP, внутренних утилит и небольших ботов.
Webhook лучше выбирать для production-сценария, где уже есть домен, HTTPS, reverse proxy, Docker и понятный процесс деплоя. В этом случае Telegram сам отправляет updates на публичный HTTPS endpoint бота. Официальный Telegram Bot API описывает setWebhook как способ указать URL, на который Telegram будет отправлять входящие updates.
Можно ли запускать Telegram-бота вручную в SSH-сессии?
Для проверки — можно. Для постоянной работы — не стоит. Если закрыть терминал, потерять SSH-соединение или перезагрузить VPS, процесс может остановиться.
Для polling-бота лучше использовать systemd, а для Docker-варианта — Docker Compose с restart policy. Так бот сможет запускаться после reboot и перезапускаться после падения.
Зачем нужен systemd для polling-бота?
systemd превращает бота из ручного процесса в управляемый сервис. Через него можно запускать, останавливать, перезапускать бота, включать автозапуск и смотреть логи.
Базовые команды:
sudo systemctl start telegram-bot
sudo systemctl enable telegram-bot
sudo systemctl status telegram-bot
sudo journalctl -u telegram-bot -f
Документация systemd описывает работу service units и механизм перезапуска сервисов через настройки unit-файла.
Где хранить токен Telegram-бота?
Токен лучше хранить в .env, а не в коде. Файл .env должен лежать на VPS и не попадать в репозиторий.
Пример:
BOT_TOKEN=123456789:AAExampleToken
ADMIN_IDS=123456789,987654321
ENV=production
В Git можно хранить только .env.example без реальных секретов. Если токен попал в публичный репозиторий, его нужно перевыпустить через BotFather.
Нужно ли использовать Docker для Telegram-бота?
Не обязательно. Для простого polling-бота через Python или Node.js часто достаточно systemd. Это проще и быстрее.
Docker полезен, если нужен более предсказуемый production-деплой: контейнер, docker-compose.yml, .env, restart policy, пересборка после git pull, изоляция зависимостей и единая схема запуска на разных серверах.
Как проверить webhook после настройки?
Нужно проверить три уровня: HTTPS, endpoint приложения и статус webhook в Telegram.
curl -I https://bot.example.com
curl -I https://bot.example.com/webhook
curl "https://api.telegram.org/bot$BOT_TOKEN/getWebhookInfo"
Если getWebhookInfo показывает last_error_message, нужно смотреть DNS, SSL, Nginx, маршрут webhook и логи контейнера.
Почему для webhook нужен HTTPS?
Telegram webhook должен быть доступен по HTTPS-адресу. Если сертификат невалидный, домен не совпадает или reverse proxy настроен неправильно, Telegram не сможет стабильно отправлять updates.
Обычно для этого используют домен, Nginx как reverse proxy и SSL-сертификат Let’s Encrypt. Nginx может принимать HTTPS-запросы и передавать их во внутреннее приложение через proxy_pass.
Как обновлять бота после изменения кода?
Для systemd-варианта:
cd /opt/telegram-bot
git pull
sudo systemctl restart telegram-bot
sudo journalctl -u telegram-bot -n 100 --no-pager
Если изменились зависимости, перед restart нужно выполнить pip install -r requirements.txt или npm install.
Для Docker-варианта:
cd /opt/telegram-bot
git pull
docker compose up -d --build
docker compose logs telegram-bot --tail=100
Docker также поддерживает restart policies, чтобы контейнеры автоматически запускались после остановки или перезагрузки в зависимости от выбранной политики.
Как ограничить админские команды?
Админские команды нужно проверять по Telegram user ID, а не по username. Username можно изменить, а числовой ID остаётся стабильнее.
Простая логика:
если user_id есть в ADMIN_IDS → выполнить команду
иначе → отказать
Так нужно закрывать рассылки, выгрузки, управление пользователями, служебные команды, статистику и любые действия, которые влияют на данные или работу бота.
Список источников
1. Telegram Bot API — setWebhook и работа с updates
2. systemd.service — unit-файлы и параметры сервисов
3. Docker Docs — автоматический запуск контейнеров и restart policy

