Systemd-сервис не запускается? Как это починить
Systemd-сервис не запускается? Учимся читать journalctl правильно, чиним пять причин большинства сбоев и возвращаем юнит в active за считаные минуты.
Сервис 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 не показывает ничего, проверьте три вещи по порядку:
StandardOutput=иStandardError=в юните — они должны бытьjournal(дефолт) илиjournal+console. Кто-то мог выставитьnull.- Приложение пишет в файл вместо stdout. Systemd перехватывает только stdout/stderr; пишущие в лог-файл проходят мимо журнала целиком. Либо направьте приложение в stdout, либо читайте файл.
- Лимиты хранилища выбросили старые строки:
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 секунд
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 сложный» сводятся к тому, что ошибка всё это время была в журнале. Читайте его до правки юнит-файла, а не после.