Systemd: как создать свой сервис на сервере Перейти к содержимому
+7 (920) 556-26-91 пн–пт: 10:00–19:00 (МСК)
Проконсультироваться Обсудить задачу

Инструкции

Systemd: как создать свой сервис и управлять им

Unit-файл для своего приложения: разбор секций, типы сервисов, переменные окружения, ограничения памяти и типичные ошибки настройки.

Systemd: как создать свой сервис и управлять им

Короткий ответ: свой сервис в systemd — это unit-файл в /etc/systemd/system/ с тремя секциями: [Unit] описывает зависимости, [Service] — что и от чьего имени запускать, [Install] — когда включать. После создания файла выполняются systemctl daemon-reload и systemctl enable --now имя. Дальше приложение запускается при загрузке, перезапускается при падении, а его вывод попадает в journald.

Для сайтов и ботов это описано в услуге разработки и поддержки сайтов; здесь — техническая часть.

Зачем systemd вместо nohup и screen

Подход Переживёт перезагрузку Перезапуск при падении Логи
nohup node app.js & Нет Нет Файл, который надо чистить
screen / tmux Нет Нет Внутри сессии
systemd Да Да, настраивается journald с ротацией
Docker + restart-политика Да Да docker logs

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

Минимальный unit-файл

Создайте /etc/systemd/system/myapp.service:

[Unit]
Description=My Application
Documentation=https://example.com/docs
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=app
Group=app
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/index.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

Разбор ключевых строк:

Директива Что делает
After=network-online.target Ждать, пока сеть действительно поднимется, а не просто инициализируется
Type=simple Основной процесс не форкается — обычный случай для Node.js, Python, Go
User / Group Процесс работает не от root. Отдельный пользователь обязателен
WorkingDirectory Рабочий каталог; относительные пути считаются от него
ExecStart Абсолютный путь к бинарнику и абсолютный путь к скрипту
Restart=on-failure Перезапуск при ненулевом коде выхода или сигнале
RestartSec=5 Пауза перед перезапуском, чтобы не устроить цикл

Путь к интерпретатору нужно указывать полностью. Если Node.js поставлен через mise, which node покажет путь внутри домашнего каталога версии — его и надо использовать, иначе systemd не найдёт бинарник, потому что переменные окружения пользователя ему недоступны.

Type=simple, forking и oneshot

Type Когда нужен
simple Процесс не отвязывается от терминала — Node.js, Python, Go, большинство приложений
forking Программа сама уходит в фон и создаёт дочерний процесс — старые демоны
oneshot Задача выполняется один раз и завершается — скрипты, миграции
notify Приложение само сообщает о готовности через sd_notify

Ошибка в Type — частая причина того, что сервис «запускается и сразу останавливается». Если приложение не форкается, а указан forking, systemd будет считать, что процесс завершился.

Управление

# перечитать unit-файлы после правки
systemctl daemon-reload

# включить автозапуск и запустить сейчас
systemctl enable --now myapp

# состояние и последние строки лога
systemctl status myapp

# логи в реальном времени
journalctl -u myapp -f

# логи за сегодня
journalctl -u myapp --since today

# только ошибки
journalctl -u myapp -p err

# перезапуск
systemctl restart myapp

# остановить и убрать из автозапуска
systemctl disable --now myapp

daemon-reload обязателен после каждой правки unit-файла. Без него systemd продолжит использовать старую версию, и изменения не применятся.

Переменные окружения и секреты

Секреты не стоит писать в Environment= — unit-файл читается всеми. Правильнее вынести их в отдельный файл:

# /etc/myapp/env  — права 600, владелец root
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://app:пароль@127.0.0.1:5432/app

И подключить в секции [Service]:

EnvironmentFile=/etc/myapp/env

Права на файл: chmod 600 /etc/myapp/env. Приложение читает переменные так же, как если бы они были заданы в unit-файле.

Ограничения ресурсов

В [Service] можно задать потолки — полезно, чтобы одно приложение не выело всю память сервера:

MemoryMax=1G
MemoryHigh=800M
CPUQuota=150%
TasksMax=256
LimitNOFILE=65535

При превышении MemoryMax процесс будет убит, и Restart=on-failure поднимет его заново. Это лучше, чем отказ всего сервера: без ограничений утечка памяти в одном сервисе роняет базу данных, nginx и всё остальное. Общий разбор темы — в материале про резервное копирование и обслуживание VPS.

Типичные ошибки

  • Относительный путь в ExecStart. systemd не ищет бинарники в PATH пользователя — только абсолютные пути.
  • Забыт daemon-reload. Правки не применяются, и это выглядит как «сервис игнорирует настройки».
  • Работа от root. Не нужно и опасно: при компрометации приложения злоумышленник получает всю машину.
  • Нет Restart. После первой же ошибки сервис останется лежать до ручного вмешательства.
  • Секреты в Environment=. Видны всем, у кого есть доступ к unit-файлу, и попадают в вывод systemctl show.
  • Type=forking для приложения, которое не форкается. systemd решит, что сервис завершился, и остановит его.

Проверка после настройки

  1. systemctl is-enabled myapp — должен ответить enabled.
  2. systemctl is-active myapp — должен ответить active.
  3. Убить процесс вручную (kill -9) и убедиться, что он поднялся заново.
  4. Перезагрузить сервер и проверить, что сервис запустился сам.
  5. Посмотреть journalctl -u myapp -p err — ошибок быть не должно.