Сервис systemd не стартует — пять причин, и лог, который их называет
Почти никогда не загадка: systemctl status, последние 50 строк journal — и одна из пяти причин названа прямым текстом. Дальше — сопоставление с образцом.
■ пространство · прозаический трек
systemctl status + journalctl -u <unit> -n 50 --no-pager — одна из пяти причин названа в логе.
203/EXEC = путь, shebang или бит исполнения — systemd запускает ExecStart= без shell.
Падает только на загрузке? Гонка очерёдности — добавьте After=/Wants= и защиту ExecStartPre=.
ДВЕ КОМАНДЫ
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. И ещё пара команд, которую стоит знать:
systemctl list-units --failed # every red unit on the box
systemctl reset-failed myapp.service # clear the failed state after the fix
verify: systemctl list-units --failed → 0 загруженных юнитов в списке
ПЯТЬ ПРИЧИН
После того как лог назвал симптом, причина почти всегда одна из этих — проверяйте сначала самую вероятную:
| Лог говорит | Вероятная причина | Фикс |
|---|---|---|
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 не использует ваш shell, чтобы запустить ExecStart= — без раскрытия ~, без PATH поиска, без cd. Смотрите, как неверные строки становятся верными:
Скрипт, который прекрасно работает из вашего терминала, но падает с 203 под systemd, — почти всегда одно из этих четырёх: не исполняем, концы CRLF, shebang в никуда или относительный путь.
РАБОТАЕТ ВРУЧНУЮ, СДЫХАЕТ ПРИ ЗАГРУЗКЕ
Загрузочный отказ — это очерёдность, не синтаксис. Классическая гонка: сервис тянется к сети, смонтированному диску или базе данных раньше, чем то появляется. Объявите зависимости, вместо того чтобы надеяться:
[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 работает, только если network-wait-сервис включён в вашем дистрибутиве — проверьте systemctl is-enabled NetworkManager-wait-online.service (или эквивалент systemd-networkd). Защита ExecStartPre= падает громко, с читабельным сообщением, вместо стектрейса.
verify: systemctl is-enabled NetworkManager-wait-online.service → enabled
Второй вариант: стартует при загрузке и тут же умирает. Ищите то, что было в вашем интерактивном окружении, но нет в буте — PATH отличающиеся, virtualenv, HOME. Systemd стартует юниты с урезанным окружением; всё, что ваш shell подставляет неявно, надо выписать явно:
[Service]
Environment="PATH=/opt/app/venv/bin:/usr/bin"
User=appuser
WorkingDirectory=/opt/app
При настоящих загадках запустите точную команду ExecStart= от пользователя юнита в shell — большинство различий окружения всплывают в первые десять секунд.
В ЖУРНАЛЕ ПУСТО?
StandardOutput=иStandardError=в юните — должны бытьjournal(по умолчанию) илиjournal+console. Возможно, кто-то поставилnull.- Приложение пишет в файл вместо stdout. Systemd ловит только stdout/stderr; пишущие в лог-файл минуют журнал целиком. Направьте вывод приложения в stdout или читайте файл.
- Лимиты хранилища выбросили старые строки:
journalctl --disk-usage, иSystemMaxUse=в/etc/systemd/journald.conf, если журнал давится.
ПЕРЕЗАПУСК ПОСЛЕ КРАША
Политика по умолчанию — Restart=no: крашнувшийся сервис остаётся мёртвым, а вы узнаёте об этом от пользователя. Правьте это сервис за сервисом:
[Service]
Restart=on-failure
RestartSec=5
verify: systemctl show myapp.service --property=Restart → Restart=on-failure
| Строка | Перезапускается когда |
|---|---|
no (умолчание) | Никогда |
on-failure | Не-нулевой код выхода, сигнал, таймаут |
always | Любой выход, даже чистый |
on-watchdog | Только таймаут watchdog |
Соединяйте Restart=on-failure с защитой от частых стартов, чтобы петля краша не таранила машину: StartLimitIntervalSec= и StartLimitBurst= в секции [Unit]. Пять отказов за шестьдесят секунд должны звать человека, а не крутить CPU вхолостую. А если вы подключаете это как запланированную работу, а не демона, таймеры даром дают журнал и порядок зависимостей — сначала взвесьте cron против таймера.
ЧЕК-ЛИСТ НА 60 СЕКУНД
systemctl status <unit>— прочитайте состояние и последние строки.journalctl -u <unit> -n 50 --no-pager— найдите настоящую ошибку.203/EXEC? Правьте путь, shebang, права.Permission denied? Правьте пользователя и владение файлами.- Падает только на загрузке? Добавьте
After=/Wants=network-online.targetи защитуExecStartPre=. - Поменяли файл юнита?
systemctl daemon-reload && systemctl restart <unit>. - Добавьте
Restart=on-failure, чтобы следующий краш объявился, а не прятался.
Большинство моментов «systemd — это сложно» сводятся к ошибка всё время была в журнале. Читайте его перед правкой файла юнита, а не после.
конец пяти причин · ничего не скачано · всё локально