Systemd-сервис не запускается? Как это починить

Systemd-сервис не запускается? Учимся читать journalctl правильно, чиним пять причин большинства сбоев и возвращаем юнит в active за считаные минуты.

HUMANавтор @mrsaynothing42d4 мин чтения

Сервис systemd, который не запускается, почти никогда не загадка. Выполните systemctl status <unit>, затем прочитайте последние 50 строк журнала по юниту через journalctl -u <unit> -n 50 --no-pager — между этими двумя командами одна из пяти причин обычно названа прямо: плохой путь, отсутствующий бинарник, неверные права, запрет от SELinux/AppArmor или синтаксическая ошибка в юнит-файле. Причина падения лежит в логе; фиксы ниже — просто сопоставление с ним по шаблону.

Как увидеть, почему systemd-сервис упал?

Сначала статус, потом журнал:

systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager

status даёт состояние (inactive (dead), failed (exit-code), activating (auto-restart)) и последние строки лога. Журнал даёт полную картину: stdout, stderr и собственные жалобы systemd на юнит.

Если юнит падал раньше и нужна запись именно того запуска, добавьте -b для текущей загрузки или --since today:

journalctl -u myapp.service -b --no-pager

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

Ещё одна пара, которую стоит знать:

systemctl list-units --failed
systemctl reset-failed myapp.service

Первая перечисляет все красные юниты на машине. Вторая сбрасывает состояние failed после починки — косметика, но страницы статуса перестают кричать «волки».

Какие причины самые частые?

Когда лог назвал симптом, причина почти всегда одна из этих пяти:

Что говорит лог Вероятная причина Решение
status=203/EXEC Неверный путь в ExecStart= или отсутствует интерпретатор Абсолютный путь, chmod +x, проверьте shebang
status=203/EXEC на скрипте У скрипта CRLF-концы строк или плохой shebang dos2unix script.sh, исправить первую строку
status=1/FAILURE, логов приложения нет Отсутствует рабочая директория или переменная окружения Задать WorkingDirectory=, добавить Environment=
Permission denied Пользователь не может читать файлы или занять порт Починить владельца; порты ниже 1024 требуют AmbientCapabilities=CAP_NET_BIND_SERVICE или root
Unit is masked Кто-то выполнил systemctl mask systemctl unmask myapp.service
Правка юнит-файла «ничего не меняет» Демон не перечитан systemctl daemon-reload

Семейство 203/EXEC заслуживает отдельного упоминания: именно оно съедает послеполуденные часы. Systemd не использует ваш шелл для запуска ExecStart=. Это значит:

# Неправильно — ни shell, ни раскрытия ~, ни поиска по PATH
ExecStart=~/app/run.sh

# Правильно
ExecStart=/opt/app/run.sh

А сам скрипт должен быть исполняемым и начинаться с настоящего shebang (#!/bin/bash или #!/usr/bin/env bash). Скрипт, который отлично работает из вашего терминала, но падает с 203 под systemd, почти всегда страдает одним из: не исполняемый, CRLF-концы строк, shebang в никуда или относительный путь.

Почему вручную стартует, а при загрузке — нет?

Классический баг порядка запуска. Если лог показывает сбои сразу после загрузки, но юнит нормально стартует руками через systemctl start, ваш сервис проигрывает гонку — он тянется к сети, смонтированному диску или базе раньше, чем та появляется.

Лечение — объявить зависимости вместо надежды:

[Unit]
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
ExecStartPre=/usr/bin/test -f /opt/app/config.toml

After= задаёт порядок старта; Wants= заставляет systemd действительно поднять зависимость. network-online.target работает, только если на вашем дистрибутиве включён сервис ожидания сети — проверьте systemctl is-enabled NetworkManager-wait-online.service (или аналог для systemd-networkd). Заглушка ExecStartPre= — дешёвый, честный способ падать громко с читаемым сообщением вместо стек-трейса.

Второй вариант: сервис стартует при загрузке, но тут же умирает. Ищите то, что было в вашем интерактивном окружении, но отсутствует при загрузке — отличия PATH, виртуальное окружение, HOME. Задайте нужное явно:

[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app

Почему сервис не пишет в журнал?

Если journalctl -u не показывает ничего, проверьте три вещи по порядку:

  1. StandardOutput= и StandardError= в юните — они должны быть journal (дефолт) или journal+console. Кто-то мог выставить null.
  2. Приложение пишет в файл вместо stdout. Systemd перехватывает только stdout/stderr; пишущие в лог-файл проходят мимо журнала целиком. Либо направьте приложение в stdout, либо читайте файл.
  3. Лимиты хранилища выбросили старые строки: journalctl --disk-usage и SystemMaxUse= в /etc/systemd/journald.conf, если журнал задыхается.

Для отладки самого старта ничто не бьёт запуск шелла в контексте загрузки:

[Service]
ExecStart=/bin/bash -c 'exec /opt/app/bin/server 2>&1'

Либо, для настоящих загадок, выполните точную команду ExecStart= от имени пользователя юнита в шелле — большинство различий окружения всплывают в первые десять секунд.

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

Дефолтная политика — Restart=no: упавший сервис остаётся мёртвым, а вы узнаёте об этом от пользователя. Исправьте это для каждого сервиса:

[Service]
Restart=on-failure
RestartSec=5
Настройка Когда перезапускает
no (дефолт) Никогда
on-failure Ненулевой код выхода, сигнал, таймаут
always Любой выход, даже чистый
on-watchdog Только таймаут watchdog

Сочетайте Restart=on-failure с защитой от шторма стартов, чтобы зацикленный краш не молотил машину: StartLimitIntervalSec= и StartLimitBurst= в секции [Unit]. Пять падений за шестьдесят секунд должны будить человека, а не крутить CPU.

Если вы собираете это как задачу по расписанию, а не как демон, сначала взвесьте cron vs systemd timer — таймеры дают журналирование в журнал и упорядочивание зависимостей из коробки, ровно то, к чему эта статья тянется снова и снова.

Чек-лист на 60 секунд

  1. systemctl status <unit> — прочитайте состояние и последние строки.
  2. journalctl -u <unit> -n 50 --no-pager — найдите настоящую ошибку.
  3. 203/EXEC? Чините путь, shebang, права. Permission denied? Чините пользователя и владельца файлов.
  4. Падает только при загрузке? Добавьте After=/Wants=network-online.target и заглушку ExecStartPre=.
  5. Правили юнит-файл? systemctl daemon-reload && systemctl restart <unit>.
  6. Добавьте Restart=on-failure, чтобы следующий краш заявил о себе, а не прятался.

Большинство моментов «systemd сложный» сводятся к тому, что ошибка всё это время была в журнале. Читайте его до правки юнит-файла, а не после.