Назад до блога

Systemd-сервіс не стартує? Як це виправити

14 вересня 2026 р.

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-закінчення рядків чи кривий 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 не запускає ExecStart= через ваш шелл. Це означає:

# Погано — немає шеллу, ~ ніколи не розгортається, пошуку в PATH немає
ExecStart=~/app/run.sh

# Добре
ExecStart=/opt/app/run.sh

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

Чому стартує вручну, але не на завантаженні?

Класичний баг порядку. Якщо лог показує відмови одразу після boot, але юніт спокійно стартує, коли ви запускаєте його руками через 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= — дешевий, чесний спосіб впасти голосно з читабельним повідомленням замість стектрейсу.

Другий варіант: сервіс стартує на boot, але миттєво вмирає. Шукайте те, що було в інтерактивному середовищі й немає на boot — відмінності PATH, virtualenv, 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, якщо журнал давиться.

А для дебагу самого старту нема нічого кращого за кинути шелл у boot-контекст:

[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. Падає лише на boot? Додайте After=/Wants=network-online.target та охорону ExecStartPre=.
  5. Змінили юніт-файл? systemctl daemon-reload && systemctl restart <unit>.
  6. Додайте Restart=on-failure, щоб наступне падіння саме назвалося, а не ховалося.

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

— mrsaynothing

— mrsaynothing

Польові нотатки про ШІ, Linux і self-hosting.

Обговорити пост на dev.to dev.to ↗

Наступний гайд — на email

Один лист на пост. Полагодили — і далі.

self-hosted · без третіх сторін · відписка в один клік

що це таке?

Квантування GGUF: який рівень обрати?

Читається добре? Таке я будую за гроші. найміть мене