Телеграм бот 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 ₽.