Разработка · 7 мин чтения

Телеграм бот VPS: запуск как сервис и автоперезапуск

Телеграм бот VPS обычно роняют одним и тем же способом: запускают командой python bot.py прямо в SSH-сессии и закрывают терминал, чтобы не занимать окно. Процесс обрывается вместе с сессией, бот перестаёт отвечать, и об этом чаще узнаёт клиент, а не разработчик. Для тестового прототипа такой запуск подходит, но для бота, которым реально пользуются, нужен процесс, переживающий разрыв SSH-соединения, перезагрузку сервера и падение самого скрипта из-за необработанного исключения. Ниже по шагам показываю, как поднять бота как systemd-сервис с автоперезапуском на обычном Linux-сервере.

Почему запуск через screen и nohup не решает проблему

screen, tmux и nohup решают только одну из трёх проблем: сессия действительно переживает закрытие SSH. На этом плюсы заканчиваются. Если бот упадёт из-за необработанного исключения в обработчике aiogram или из-за обрыва сети до Telegram API, процесс никто не перезапустит, пока кто-то вручную не зайдёт на сервер и не проверит, жив ли он вообще.

У меня был случай, когда после планового перезапуска VPS для обновления ядра бот у клиента не поднялся сам: он был запущен через nohup, а после ребута его никто заново не стартовал. Клиенты писали боту около трёх часов, прежде чем кто-то из команды заметил тишину и зашёл на сервер руками.

Systemd закрывает обе проблемы сразу. Он следит за процессом бота как за системным сервисом: поднимает его при загрузке сервера, перезапускает при падении и хранит логи в одном месте через journalctl, без отдельного файла под каждый перезапуск.

Настройка systemd-сервиса для телеграм-бота на VPS

Дальше описываю настройку для бота на Python с aiogram, но принцип для бота на любом языке тот же: сервис systemd запускает исполняемый файл и следит за его состоянием.

Юнит-файл сервиса

Юнит-файл описывает, как запускать бота, от чьего имени и что делать при падении процесса. Создаю файл через sudo и сразу прописываю нужные параметры:

sudo tee /etc/systemd/system/mybot.service > /dev/null << 'EOF'
[Unit]
Description=Telegram bot mybot
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=5

[Service]
Type=simple
User=botuser
WorkingDirectory=/home/botuser/mybot
ExecStart=/home/botuser/mybot/venv/bin/python3 /home/botuser/mybot/bot.py
Restart=on-failure
RestartSec=5
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target
EOF

Что здесь важно:

  • User запускает бота не от root, а от отдельного пользователя, чтобы упавший процесс не имел лишних прав в системе.
  • ExecStart указывает полный путь и к интерпретатору из виртуального окружения, и к самому файлу бота, относительный путь systemd не воспримет, потому что стартует не из директории проекта.
  • Restart=on-failure перезапускает процесс при ошибке, но не трогает бота, если я сам останавливаю его командой systemctl stop.
  • StartLimitIntervalSec и StartLimitBurst не дают systemd уйти в бесконечный цикл перезапуска, если в коде реальная ошибка.

Запуск, автозагрузка и проверка статуса

sudo systemctl daemon-reload
sudo systemctl enable mybot.service
sudo systemctl start mybot.service
sudo systemctl status mybot.service

systemctl enable добавляет сервис в автозагрузку, поэтому после перезагрузки сервера, например после планового обновления пакетов, бот поднимется сам, без захода по SSH. Статус проверяю командой systemctl status mybot.service, там же видны PID процесса и последние строки лога.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Автоперезапуск при сбоях и после перезагрузки сервера

Restart=on-failure перезапускает бота, если процесс завершился с ошибкой или его убил сигнал, но не мешает штатной остановке через systemctl stop. На плановую остановку выбор значения не влияет: systemd не перезапускает сервис, если его остановили командой systemctl stop или равнозначной операцией, и это верно в том числе для Restart=always. Разница в другом: always поднимает процесс и после штатного завершения с нулевым кодом, а on-failure только после ошибки, сигнала, таймаута или срабатывания watchdog.

Значение Restart Когда перезапускает процесс
no никогда не перезапускает, значение по умолчанию
on-failure при ненулевом коде выхода или завершении по сигналу, ручной stop не трогает
always при любом завершении процесса, но не после остановки через systemctl stop
on-abnormal при аварийном завершении по сигналу или таймауту, не при обычной ошибке в коде

RestartSec=5 добавляет паузу перед перезапуском. Без неё, если бот падает сразу после старта, например из-за неверного токена в переменных окружения, systemd начинает перезапускать его по нескольку раз в секунду, и в логах это выглядит как метель из одинаковых строк. StartLimitBurst=5 и StartLimitIntervalSec=60 останавливают этот цикл: если сервис не продержался пять попыток за минуту, systemd прекращает попытки и помечает сервис как failed, вместо того чтобы бесконечно грузить сервер перезапуском.

После такой остановки бот сам больше не поднимется, придётся разобраться в причине падения и запустить сервис вручную командой systemctl restart mybot.service.

Логи и мониторинг бота на выделенном сервере

Если ExecStart запускает процесс напрямую, без перенаправления вывода в отдельный файл, все print() и необработанные исключения из бота попадают в journalctl. Живые логи смотрю командой sudo journalctl ‑u mybot.service ‑f, а разобраться, что случилось час назад, помогает journalctl -u mybot.service --since "1 hour ago".

Для критичных ошибок дополнительно вешаю в aiogram глобальный обработчик исключений, который отправляет текст ошибки в отдельный телеграм-чат для админов. Так о падении узнаю раньше, чем клиент напишет в поддержку, и не приходится держать открытым journalctl ‑f весь день.

Альтернативы systemd: pm2 для Node.js и n8n для сценариев без кода

Если бот написан на Node.js, например через Telegraf, вместо systemd-юнита часто ставят pm2:

pm2 start bot.js --name mybot
pm2 save
pm2 startup

pm2 start запускает процесс, pm2 save сохраняет список запущенных процессов, а pm2 startup генерирует systemd-юнит для самого pm2, чтобы он поднимался при перезагрузке сервера вместе со всеми процессами под своим управлением. По факту это тот же systemd, только со своей прослойкой и более удобным CLI под Node-процессы.

Отдельный случай, когда бот нужен не для сложной логики на своём коде, а чтобы принять сообщение и передать его дальше по цепочке действий: записать заявку в таблицу, отправить уведомление в CRM, дёрнуть вебхук. Для таких сценариев вместо кода на aiogram беру автоматизацию в n8n, там телеграм-триггер и готовые ноды собираются в сценарий без своего кода на процесс. Но n8n на VPS - это тоже отдельный сервис, который нужно развернуть, обновлять и держать под тем же systemd или Docker-контейнером с автозапуском, сам себя он не обслуживает.

Обновление кода бота без ручного захода на сервер

Когда меняю код бота, процесс на VPS не подхватывает изменения на лету, python держит в памяти уже загруженный файл. После git pull перезапускаю сервис командой systemctl restart mybot.service, это быстрее, чем stop и start по отдельности, и systemd сам обрабатывает остановку старого процесса перед стартом нового.

Для проектов с частыми обновлениями обычно оформляю это отдельным bash-скриптом на сервере: git pull, установка зависимостей из requirements.txt, если файл менялся, и restart сервиса одной командой. Ручной заход по SSH тогда сводится к одному вызову скрипта вместо цепочки из четырёх-пяти команд подряд.

Условия и возможности Telegram меняются, поэтому перед запуском я уточняю их у самого сервиса.

Частые вопросы

Нужен ли отдельный VPS только под одного телеграм-бота?

Для одного лёгкого бота обычно хватает самого простого тарифа VPS, aiogram и systemd почти не грузят процессор в простое. Смысл более мощного сервера появляется, когда на нём же крутится база данных, несколько ботов или фоновые задачи с очередями. Если сомневаетесь, какой конфигурации хватит под вашу задачу, могу оценить это на консультации, от 3 000 ₽.

Что делать, если systemctl status показывает active (running), а бот не отвечает?

Значит процесс жив, но завис внутри, например ждёт ответа от внешнего API без таймаута или упёрся в блокировку при работе с базой. Сначала смотрю journalctl ‑u mybot.service ‑f на предмет последних строк перед зависанием, а в коде добавляю таймауты на все внешние запросы, чтобы одно медленное соединение не вешало весь бот целиком.

Как перезапускать бота автоматически при обновлении кода?

Через CI один из вариантов: скрипт деплоя по SSH делает git pull и systemctl restart mybot.service после каждого пуша в основную ветку. Без CI то же самое оформляется простым bash-скриптом на сервере и запускается вручную после git pull, важно не забывать про restart, иначе изменения в коде не применятся, пока процесс не перезапущен.

Можно ли обойтись без VPS и запустить бота на обычном хостинге сайта?

Обычный shared-хостинг для сайта на Tilda или WordPress под постоянно работающий python-процесс не годится: там нет доступа для запуска фонового процесса и systemd, хостинг рассчитан на PHP-скрипты, которые выполняются по запросу и завершаются. Для бота на aiogram или Telegraf нужен именно сервер с доступом по SSH и правами на systemd, то есть VPS. Если настройка сервера пугает, разработку и настройку бота под ключ беру от 30 000 ₽.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно