■ пространство · прозаический трек

■ сначала ответ

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 или неверный shebangdos2unix script.sh, почините первую строку
status=1/FAILURE, логов приложения нетРабочий каталог или переменная окружения отсутствуютЗадайте WorkingDirectory=, добавьте Environment=
Permission deniedПользователь не может читать файлы или занять портПоправьте владение; порты ниже 1024 требуют AmbientCapabilities=CAP_NET_BIND_SERVICE или root
Unit is maskedКто-то запустил systemctl masksystemctl unmask myapp.service
Правка файла юнита «ничего не меняет»Демон не перечитанsystemctl daemon-reload

СЕМЕЙСТВО 203/EXEC

Та, что съедает полдни. Systemd не использует ваш shell, чтобы запустить ExecStart= — без раскрытия ~, без PATH поиска, без cd. Смотрите, как неверные строки становятся верными:

ExecStart=~/app/run.shExecStart=/opt/app/run.shнет shell → нет раскрытия тильды, нужен абсолютный путь
ExecStart=run.shExecStart=/opt/app/run.shнет поиска по PATH — относительные пути умирают первыми
(скрипт неисполняем, концы CRLF)chmod +x run.sh && dos2unix run.shскрипту нужны бит исполнения и настоящий shebang: #!/bin/bash или #!/usr/bin/env bash

Скрипт, который прекрасно работает из вашего терминала, но падает с 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 — большинство различий окружения всплывают в первые десять секунд.

В ЖУРНАЛЕ ПУСТО?

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

  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 — это сложно» сводятся к ошибка всё время была в журнале. Читайте его перед правкой файла юнита, а не после.

■ честный штамп Инструменты: systemd · journalctl · файлы юнитов. Первая публикация 2026-09-14, карточка обновлена 2026-10-04. Пять причин, механика 203/EXEC и политики перезапуска — документированное поведение systemd; каждый кусок юнита выше — та форма, что реально живёт на машинах: набрано с юнитов, которые падали и возвращались.

конец пяти причин · ничего не скачано · всё локально