Настройка systemd на сервере: рабочие приёмы
Один unit-файл редко делает сервис надёжным. Разбираем настройку systemd 252+ для серверных приложений: автозапуск, перезапуск, переменные окружения, логи, зависимости и базовое ограничение прав.

Сервис под systemd может пережить перезагрузку, автоматически подняться после сбоя и оставить диагностируемый журнал — но только если unit-файл описывает не одну команду запуска, а весь жизненный цикл процесса. Ниже — практические приёмы для systemd 252+ на Linux-сервере: от базового unit до проверки отказов и ограничений.
Что именно настраивать
Рассмотрим типичный backend или worker, который запускается от отдельного пользователя, слушает локальный порт и должен стартовать после сети. У вас понадобятся SSH-доступ с правами sudo, установленный systemd 252 или новее, бинарник или скрипт приложения и отдельный системный пользователь.
- Проверьте версию:
systemd --version. В статье используется синтаксис, доступный в systemd 252+. - Положите unit-файл в
/etc/systemd/system/. Файлы из этого каталога предназначены для локальных сервисов и имеют приоритет над системными шаблонами. - Запускайте приложение не от root, если ему не нужны привилегированные порты или устройства.
- Заранее определите каталог приложения, пользователя, порт и место хранения данных.
Примечание: unit — это описание ресурса, которым управляет systemd. Для серверного процесса обычно используется unit типа service.
Базовый unit: минимум, который не стыдно оставить в проде
Создайте файл /etc/systemd/system/orders-api.service. Названия секций и директив чувствительны к точному синтаксису, поэтому не заменяйте их похожими вариантами из примеров для SysVinit.
[Unit]
Description=Orders API
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=orders
Group=orders
WorkingDirectory=/srv/orders-api
ExecStart=/srv/orders-api/bin/orders-api
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
After= задаёт порядок запуска, но сам по себе не включает зависимость. Поэтому здесь указаны две директивы: Wants= просит systemd активировать цель сети, а After= не даёт сервису стартовать раньше неё. Это не проверка доступности конкретной базы данных или внешнего API.
Примените unit и включите его:
sudo systemctl daemon-reload
sudo systemctl enable --now orders-api.service
sudo systemctl status orders-api.service --no-pager
Проверка результата: в статусе должна быть строка Active: active (running). Если приложение слушает порт, проверьте его отдельно командой, подходящей вашей системе, например ss -ltnp.
Три рабочих подхода к запуску
Один и тот же процесс можно запускать по-разному. Выбор влияет на наблюдаемость, безопасность и то, как systemd понимает завершение приложения.
| Подход | Когда использовать | Цена и риск |
|---|---|---|
Type=simple | Приложение остаётся на переднем плане и не создаёт дочерний демон | Самый простой вариант; systemd не ждёт отдельного сигнала готовности |
Type=exec | Нужно отличать успешный запуск команды от момента создания процесса | Требует systemd с поддержкой этой директивы; поведение лучше проверять на целевой ОС |
Type=notify | Приложение умеет сообщать systemd, что инициализация завершена | Нужна поддержка sd_notify в приложении или его рантайме; без неё сервис может зависнуть в состоянии запуска |
Для обычного бинарника или процесса, который не умеет отправлять уведомления systemd, начинайте с Type=simple. Не ставьте Type=forking «по привычке»: этот режим предназначен для программ, которые сами уходят в фон, и требует корректного поведения PID-файла или процесса-родителя.
Главная ошибка в unit-файлах — описывать желаемый результат, а не наблюдаемое поведение программы: systemd может перезапустить процесс, но не угадает, готов ли он принимать запросы.
Перезапуск без бесконечного цикла
Restart=on-failure перезапускает сервис после аварийного завершения, но не после штатной остановки командой systemctl stop. Это безопаснее, чем безусловный Restart=always: ошибка в конфигурации не должна превращаться в непрерывную очередь перезапусков.
Ограничьте скорость рестартов отдельными параметрами:
[Service]
Restart=on-failure
RestartSec=5s
StartLimitIntervalSec=60s
StartLimitBurst=5
За 60 секунд systemd разрешит не более пяти запусков. При превышении лимита unit перейдёт в состояние ошибки, а не продолжит бесконечно занимать CPU и писать одинаковые сообщения в журнал. После исправления причины выполните sudo systemctl reset-failed orders-api.service, затем запустите unit снова.
Если приложение завершилось с кодом 0, а это для него означает штатную остановку, on-failure не будет считать событие сбоем. Проверьте реальные коды завершения по журналу и статусу, а не выбирайте значение Restart= вслепую.
Переменные, секреты и рабочий каталог
Небольшие несекретные настройки можно задать через Environment=, но для длинного набора параметров удобнее файл окружения:
[Service]
EnvironmentFile=/etc/orders-api/orders-api.env
WorkingDirectory=/srv/orders-api
ExecStart=/srv/orders-api/bin/orders-api
Права на файл с секретами должны исключать чтение для посторонних пользователей, например владельцем может быть root, а режим — 0600. Не помещайте ключи в сам unit-файл, историю shell или репозиторий. После изменения файла окружения нужен перезапуск: уже запущенный процесс не получает новые переменные автоматически.
Проверяйте итоговую конфигурацию так:
sudo systemctl cat orders-api.service
sudo systemctl show orders-api.service --property=User,WorkingDirectory,ExecStart,EnvironmentFiles
В выводе systemctl show могут быть видны значения переменных. Не публикуйте этот вывод без очистки, если среди настроек есть секреты.
Логи и диагностика сбоя
Если приложение пишет в stdout и stderr, systemd обычно передаёт эти потоки в journald. Начните диагностику с последних записей:
sudo journalctl -u orders-api.service -n 100 --no-pager
sudo journalctl -u orders-api.service -f
sudo systemctl status orders-api.service --no-pager
Команда с ключом -f показывает новые записи в реальном времени. Смотрите не только на последнюю строку: причина часто появляется раньше, например в сообщении о недоступном файле конфигурации или занятом порте.
Разделяйте три случая: процесс не запустился, процесс запустился и сразу завершился, процесс работает, но не принимает трафик. Для первого проверяйте путь в ExecStart и права. Для второго — код завершения и журнал. Для третьего — порт, bind-адрес, firewall и готовность зависимостей.
Базовое ограничение прав
Запуск от отдельного пользователя — первый и самый дешёвый слой защиты. Дальше можно добавить ограничения, но каждое из них проверяйте на реальном приложении: некоторые фреймворки создают временные файлы, читают сертификаты или используют локальный сокет.
[Service]
User=orders
Group=orders
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/srv/orders-api/var
NoNewPrivileges=true запрещает процессу и его потомкам получать новые привилегии. PrivateTmp=true даёт изолированный временный каталог. ProtectSystem=full ограничивает запись в системные каталоги, а ReadWritePaths= оставляет явно указанный путь доступным для записи. Если приложению нужен другой каталог, добавьте его осознанно, а не отключайте всю защиту.
После изменения ограничений смотрите журнал на сообщения о запрете доступа. Проверяйте не только старт, но и основные операции: загрузку файла, запись результата, ротацию логов и подключение к Unix-сокету.
Что ломается чаще всего
- Изменили unit, но сервис использует старую версию. Выполните
systemctl daemon-reload, затем перезапустите сервис. - Вручную запускается, через systemd — нет. У systemd другой пользователь, рабочий каталог и окружение. Укажите абсолютные пути в
ExecStartи проверьтеUser. - Статус active, но запросы не проходят. Проверьте, на каком адресе и порту приложение слушает, а также порядок старта reverse proxy.
- Сервис постоянно перезапускается. Посмотрите
journalctl, код выхода и сообщения об ограничении запуска. Не увеличивайте лимиты, пока не устранена причина падения. - После добавления защиты пропала запись в каталог. Сверьте путь с
ReadWritePathsи права владельца. Разрешайте только необходимый каталог.
Что проверить перед передачей в прод
Остановите процесс, перезагрузите сервер и убедитесь, что сервис стартует сам. Затем намеренно завершите его с тестовым сигналом в согласованное окно и проверьте, что Restart=on-failure срабатывает. Для systemd 252+ полезно также посмотреть итоговые свойства через systemctl show, а не полагаться только на содержимое исходного файла: параметры могут приходить из drop-in-конфигураций.
Следующим шагом настройте наблюдение за метриками, которых systemd не знает: временем готовности приложения, числом ответов 5xx, задержкой и доступностью зависимостей. Сам unit отвечает за процесс, но не заменяет health check и мониторинг бизнес-функции.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: systemd.service — официальная документация, systemd.exec — официальная документация
Частые вопросы
Где хранить unit-файл для собственного сервиса?
Чем отличаются Restart=on-failure и Restart=always?
Почему приложение запускается из shell, но не через systemd?
Нужно ли использовать daemon-reload после каждого перезапуска?
Что делать, если сервис попал в Start request repeated too quickly?
Можно ли сразу включить ProtectSystem и ReadWritePaths?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.