Бот, запущенный в screen или через nohup, останавливается при первой ошибке и не поднимается после перезагрузки сервера. Ниже — как развернуть Telegram-бота на сервере правильно: как службу systemd, которая стартует вместе с системой, перезапускается после сбоя, пишет логи в системный журнал и работает от отдельного пользователя без лишних прав. Разберём бота на Python и на Node.js — отличается только установка и одна строка в настройках службы.

Проверено на Ubuntu 24.04 и 26.04: Python 3.12 и 3.14 с aiogram 3.31.0, Node.js 24.21.0 с grammY 1.46.0, systemd 255 и 259. Код ботов проверен на тестовом сервере, который отвечает как Bot API Telegram.

Что нужно заранее

  • Сервер с Ubuntu 24.04 или 26.04 и пользователь admin с правами sudo — как его создать, описано в первой настройке сервера.
  • Токен бота от @BotFather — строка вида 123456789:AA….
  • Код бота, лучше в git-репозитории. Если кода пока нет, возьмите пример из статьи.

Бота будем называть echobot: служба echobot.service, пользователь echobot, каталог /opt/echobot. Подставьте своё имя.

Шаг 1. Отдельный пользователь для бота

Бот не должен работать от root или от вашего пользователя: если в коде или библиотеке найдётся уязвимость, злоумышленник получит только права бота.

sudo useradd --system --no-create-home --shell /usr/sbin/nologin echobot
id echobot

--system создаёт служебную учётную запись, nologin запрещает под ней входить. Код будет принадлежать admin — бот сможет его читать, но не изменять:

sudo install -d -o admin -g admin /opt/echobot
git clone https://github.com/you/echobot.git /opt/echobot    # если код в git

Писать бот сможет только в каталог data — туда кладите всё, что он сохраняет в файлы (SQLite, загрузки). Добавьте data/ и .venv/ в .gitignore.

sudo install -d -o echobot -g echobot -m 750 /opt/echobot/data

Шаг 2. Python-бот: виртуальное окружение и aiogram

Возьмём aiogram — популярную асинхронную библиотеку для ботов на Python. На 11 октября 2026 года последняя версия — 3.31.0, она работает с Python 3.10–3.14, то есть с системным Python обеих версий Ubuntu.

sudo apt update
sudo apt install -y python3-venv git
cd /opt/echobot
python3 -m venv .venv
echo 'aiogram==3.31.0' > requirements.txt    # если файла ещё нет в репозитории
.venv/bin/pip install -r requirements.txt
.venv/bin/python -c 'import aiogram; print(aiogram.__version__)'

Виртуальное окружение .venv — отдельная копия библиотек для этого проекта: обновление системных пакетов его не сломает. Точная версия в requirements.txt нужна, чтобы на сервере стояло то же, что вы проверили у себя.

Сам бот — файл /opt/echobot/bot.py. Он отвечает на /start и повторяет присланный текст:

import asyncio
import logging
import os

from aiogram import Bot, Dispatcher, F
from aiogram.filters import CommandStart
from aiogram.types import Message

dp = Dispatcher()


@dp.message(CommandStart())
async def start(message: Message) -> None:
    await message.answer("Бот работает. Пришлите текст — я его повторю.")


@dp.message(F.text)
async def echo(message: Message) -> None:
    await message.answer(message.text)


async def main() -> None:
    logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s")
    bot = Bot(token=os.environ["BOT_TOKEN"])
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Обратите внимание: токена в коде нет, бот берёт его из переменной окружения BOT_TOKEN. Время в формате логов не нужно — его добавит системный журнал.

Шаг 3. Токен — в отдельном файле с правами 600

Токен даёт полный доступ к боту. Его не пишут в код и не кладут в git — только в файл на сервере, который может прочитать один root:

sudo install -d -m 700 /etc/echobot
sudo install -m 600 /dev/null /etc/echobot/bot.env
sudo nano /etc/echobot/bot.env

В файл одна строка, без кавычек и пробелов вокруг =:

BOT_TOKEN=123456789:AA-ваш-токен

Редактор, а не echo … | sudo tee, — чтобы токен не остался в истории команд. Проверьте права: sudo ls -l /etc/echobot/bot.env должен показать -rw------- 1 root root. Systemd читает этот файл от имени root до запуска бота и передаёт значение в окружение процесса, поэтому самому пользователю echobot доступ к файлу не нужен — и его нет.

Шаг 4. Unit-файл systemd

Unit-файл описывает службу: что запускать, от чьего имени и что делать при сбое. Создайте /etc/systemd/system/echobot.service:

sudo nano /etc/systemd/system/echobot.service
[Unit]
Description=Echo Telegram bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=echobot
Group=echobot
WorkingDirectory=/opt/echobot
EnvironmentFile=/etc/echobot/bot.env
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/echobot/.venv/bin/python bot.py
Restart=on-failure
RestartSec=5

NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/opt/echobot/data
ProtectHome=yes
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

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

  • After= и Wants=network-online.target — запускать бота после того, как поднялась сеть, а не просто после старта сетевой службы.
  • EnvironmentFile — файл с токеном из шага 3.
  • PYTHONUNBUFFERED=1 — без этой строки Python копит вывод print() в буфере, и в журнале его не видно. Мы проверили: без неё строки print() в журнал не попали.
  • ExecStart — полный путь к Python из виртуального окружения; активировать окружение отдельно не нужно.
  • Restart=on-failure — перезапуск, если процесс завершился с ошибкой или его убили. Если бот завершился сам с кодом 0, systemd его не перезапускает. Нужен перезапуск в любом случае — пишите Restart=always.
  • RestartSec=5 — пауза перед перезапуском. По умолчанию systemd прекращает попытки после 5 запусков за 10 секунд; с паузой в 5 секунд этот предел не достигается, и бот перезапускается, пока не заработает.

Последний блок — ограничения, которые почти ничего не стоят и сильно сужают возможности взломанного бота:

  • NoNewPrivileges=yes — процесс и его потомки не могут получить больше прав, например через sudo.
  • ProtectSystem=strict — вся файловая система для бота только для чтения, кроме каталогов из ReadWritePaths.
  • ProtectHome=yes — каталоги /home и /root для бота недоступны.
  • PrivateTmp=yes — у бота свой /tmp, не общий с другими программами.

Проверьте файл на ошибки:

sudo systemd-analyze verify /etc/systemd/system/echobot.service

Пустой вывод — ошибок нет.

Шаг 5. Запуск и автозапуск

sudo systemctl daemon-reload
sudo systemctl enable --now echobot
systemctl status echobot

daemon-reload перечитывает unit-файлы, enable --now включает автозапуск при загрузке и сразу запускает бота. В выводе status должно быть Active: active (running) и enabled в строке Loaded. Напишите боту в Telegram /start — он ответит.

Мы проверили ограничения тестовым скриптом, запущенным в этой же службе. Запись в /opt/echobot/data работает; запись в /opt/echobot и /etc — Read-only file system; /home — Permission denied; sudo отвечает The "no new privileges" flag is set; файл /etc/echobot/bot.env бот прочитать не может, но переменная BOT_TOKEN у него есть.

Логи бота: journalctl

Всё, что бот пишет в консоль, попадает в системный журнал. Пользователь admin без группы adm журнала служб не видит (-- No entries --), поэтому команды — через sudo:

sudo journalctl -u echobot -f                    # смотреть в реальном времени, выход — Ctrl+C
sudo journalctl -u echobot -n 100                # последние 100 строк
sudo journalctl -u echobot --since "1 hour ago"  # за последний час
sudo journalctl -u echobot --since today -g Error   # только строки со словом Error

После запуска в журнале будут строки aiogram:

INFO aiogram.dispatcher: Start polling
INFO aiogram.dispatcher: Run polling for bot @echo_test_bot id=123456789 - 'Echo'
INFO aiogram.event: Update id=1 is handled. Duration 14 ms by bot id=123456789

Ошибки Python тоже приходят в журнал целиком, вместе с трассировкой. Фильтр -p err для них не поможет: всё, что программа пишет в консоль, журнал записывает с обычным приоритетом, поэтому ищите по тексту через -g.

Как проверить автоперезапуск

Имитируем сбой — убьём процесс бота:

sudo kill -9 $(systemctl show -p MainPID --value echobot)
sleep 7
sudo journalctl -u echobot -n 4 --no-pager

В журнале появятся строки Scheduled restart job, restart counter is at 1 и Started echobot.service. Сколько раз служба перезапускалась с момента запуска, покажет systemctl show echobot -p NRestarts.

Автозапуск после перезагрузки проверьте командой sudo reboot: после входа systemctl is-active echobot должен ответить active.

Как обновлять бота

Если код в git:

cd /opt/echobot
git pull --ff-only
.venv/bin/pip install -r requirements.txt
sudo systemctl restart echobot
sudo journalctl -u echobot -n 20 --no-pager

--ff-only не даёт git молча создать слияние, если на сервере кто-то правил файлы руками. Установка зависимостей нужна, только если менялся requirements.txt, но лишней она не будет. Последняя команда — убедиться, что новая версия запустилась без ошибок. Во время перезапуска бот не отвечает несколько секунд; сообщения, присланные за это время, Telegram хранит (до 24 часов) и отдаст после запуска.

Если меняли сам unit-файл, перед перезапуском выполните sudo systemctl daemon-reload.

Бот на Node.js: установка и запуск

В репозитории Ubuntu версии Node.js устаревшие: в 24.04 — 18.19 (поддержка закончилась в апреле 2025 года), в 26.04 — 22.22. На 11 октября 2026 года актуальная LTS-версия — Node.js 24, она поддерживается до апреля 2028 года (график релизов). Node.js 26 станет LTS 28 октября 2026 года — после этого можно ставить его, заменив 24 на 26 в адресе репозитория.

Поставим Node.js 24 из репозитория NodeSource:

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key -o /etc/apt/keyrings/nodesource.asc
sudo tee /etc/apt/sources.list.d/nodesource.sources > /dev/null <<EOF
Types: deb
URIs: https://deb.nodesource.com/node_24.x
Suites: nodistro
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/nodesource.asc
EOF
sudo apt update
apt policy nodejs          # Candidate: 24.x.x-1nodesource1
sudo apt install -y nodejs
node -v                    # v24.21.0

Шаги 1 и 3 — пользователь, каталоги, файл с токеном — те же. Библиотека — grammY, версия 1.46.0:

cd /opt/echobot
npm init -y
npm install --save-exact grammy@1.46.0

Код — файл /opt/echobot/bot.mjs (расширение .mjs включает современный синтаксис import без настроек):

import { Bot } from "grammy";

const bot = new Bot(process.env.BOT_TOKEN);

bot.command("start", (ctx) => ctx.reply("Бот работает. Пришлите текст — я его повторю."));
bot.on("message:text", (ctx) => ctx.reply(ctx.message.text));
bot.catch((err) => console.error("Ошибка при обработке update:", err.error));

process.once("SIGTERM", () => bot.stop());
process.once("SIGINT", () => bot.stop());

await bot.start({ onStart: (me) => console.log(`Бот @${me.username} запущен`) });

bot.catch не даёт ошибке в одном обработчике уронить весь бот. Обработка SIGTERM нужна, чтобы systemctl stop и restart останавливали бота аккуратно, а не по таймауту.

В unit-файле из шага 4 отличаются две строки:

Environment=NODE_ENV=production
ExecStart=/usr/bin/node bot.mjs

Остальное, включая ограничения, без изменений. Обновление — как для Python, только вместо pip команда npm ci --omit=dev: она ставит ровно то, что записано в package-lock.json.

Long polling или webhook

Оба бота выше работают через long polling: сами раз за разом спрашивают у Telegram новые сообщения (метод getUpdates). Это самый простой вариант: не нужны домен, открытые порты и сертификат, бот работает даже за NAT.

Webhook — обратная схема: Telegram сам отправляет каждое сообщение на ваш адрес. Для него нужны домен и HTTPS с действующим сертификатом на одном из портов 443, 80, 88 или 8443 (документация Bot API), обычно — веб-сервер перед ботом. Webhook имеет смысл при большом потоке сообщений или когда бот и так работает внутри веб-приложения. Для большинства ботов на одном сервере long polling достаточно.

Одновременно работает только один способ: пока у бота установлен webhook, getUpdates возвращает ошибку. grammY при запуске в режиме polling сам снимает webhook, aiogram — нет.

Если не работает

Что видно Что делать
status=203/EXEC в systemctl status Неверный путь в ExecStart. Проверьте, что файл существует: ls -l /opt/echobot/.venv/bin/python или which node
KeyError: 'BOT_TOKEN' (Python) или Empty token! (grammY) Нет переменной: проверьте путь в EnvironmentFile и формат строки BOT_TOKEN=… без пробелов
Unauthorized в журнале, бот перезапускается каждые 5 секунд Неверный токен. Исправьте /etc/echobot/bot.env и выполните sudo systemctl restart echobot
Conflict: terminated by other getUpdates request Тот же токен опрашивает другой процесс — например, бот запущен ещё и на вашем компьютере. Остановите второй экземпляр
can't use getUpdates method while webhook is active У бота остался webhook. Снимите его: откройте https://api.telegram.org/bot<токен>/deleteWebhook
Read-only file system или Permission denied при записи Бот пишет вне /opt/echobot/data. Перенесите файлы туда или добавьте каталог в ReadWritePaths
В журнале нет вывода print() Нет строки Environment=PYTHONUNBUFFERED=1
journalctl отвечает -- No entries -- Запустите через sudo
Правки unit-файла не действуют sudo systemctl daemon-reload, затем sudo systemctl restart echobot

Какие ещё ограничения можно включить для службы, подскажет systemd-analyze security echobot: она перечисляет все настройки защиты и отмечает включённые.

Что дальше

  • Если сервер новый, сначала пройдите первую настройку Ubuntu и настройте вход по SSH-ключу.
  • Если у бота появилась база данных или веб-панель, удобнее запускать всё вместе в контейнерах — см. Docker Compose в продакшене на одном VPS.
  • Сколько памяти занимает бот и как ограничить её для службы (MemoryMax) — в статьях о размере сервера и о нехватке памяти.
  • Узнать, что бот упал, раньше пользователей — Uptime Kuma и уведомления в Telegram.
  • Если бот — это цепочка «получить сообщение — обработать — ответить» без сложной логики, её можно собрать без кода в n8n на своём сервере.
  • Подробнее о журнале служб и его размере — логи на сервере: journalctl и logrotate; установка и обновление Node.js (в Node 26 нет corepack) — как установить Node.js на сервер; веб-приложение на Python по той же схеме — FastAPI и Django на VPS.