Wróć do bloga

Usługa systemd nie startuje? Jak to naprawić

14 września 2026

Usługa systemd, która nie chce wystartować, prawie nigdy nie jest zagadką. Odpal systemctl status <unit>, a potem przeczytaj ostatnie 50 linii journala dla tego unitu przez journalctl -u <unit> -n 50 --no-pager — między tymi dwoma zwykle jedna z pięciu przyczyn nazywa się czarno na białym: zła ścieżka, brakujący plik wykonywalny, złe uprawnienia, odmowa SELinux/AppArmor albo błąd składni w pliku unit. Powód awarii siedzi w logu; poniższe naprawy to zwykłe dopasowanie wzorca do niego.

Jak zobaczyć, czemu usługa systemd nie wstała?

Najpierw status, potem journal:

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

status daje ci stan (inactive (dead), failed (exit-code), activating (auto-restart)) i ostatnie linie logu. Journal daje pełną historię: stdout, stderr i własne narzekania systemd na unit.

Jeśli unit padł wcześniej i chcesz zapis tamtego uruchomienia, dołóż -b dla bieżącego bootu albo --since today:

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

Pełny zestaw narzędzi — bootay, priorytety, podgląd wyjścia na żywo — znajdziesz w ściągu journalctl. To ten sam odruch palców.

Jeszcze jedna para warta znajomości:

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

Pierwsza wypisuje każdy czerwony unit na maszynie. Druga czyści stan failed po naprawie — kosmetyka, ale przestaje strony statusowe wołać wilka.

Jakie są najczęstsze przyczyny?

Kiedy log nazwie objaw, przyczyna prawie zawsze jest jedną z tych pięciu:

Log mówiPrawdopodobna przyczynaNaprawa
status=203/EXECZła ścieżka ExecStart= albo brakujący interpreterŚcieżka bezwzględna, chmod +x, sprawdź shebang
status=203/EXEC przy skrypcieSkrypt ma końce linii CRLF albo zły shebangdos2unix script.sh, napraw pierwszą linię
status=1/FAILURE, brak logów aplikacjiBrakuje katalogu roboczego albo zmiennej środowiskowejUstaw WorkingDirectory=, dołóż Environment=
Permission deniedUżytkownik nie może czytać plików albo związać portuNapraw właściciela; porty poniżej 1024 wymagają AmbientCapabilities=CAP_NET_BIND_SERVICE albo roota
Unit is maskedKtoś odpalił systemctl masksystemctl unmask myapp.service
Edycja unitu „nic nie robi”Demon nieprzeładowanysystemctl daemon-reload

Rodzina 203/EXEC zasługuje na osobną wzmiankę, bo to ona zjada popołudnia. Systemd nie używa twojego shella do odpalania ExecStart=. To znaczy:

# Źle — brak shella, ~ nigdy się nie rozwija, brak przeszukiwania PATH
ExecStart=~/app/run.sh

# Dobrze
ExecStart=/opt/app/run.sh

A sam skrypt musi być wykonywalny i zaczynać się od prawdziwego shebanga (#!/bin/bash albo #!/usr/bin/env bash). Skrypt, który z twojego terminala chodzi, a pod systemd pada z 203, to prawie zawsze jedno z: brak bitu wykonywalności, końce CRLF, shebang w nikąd albo ścieżka względna.

Czemu startuje ręcznie, a nie przy bootcie?

Klasyczny bug kolejności. Jeśli log pokazuje awarie tuż po bootcie, ale unit startuje poprawnie, gdy odpalisz go ręką przez systemctl start, twoja usługa przegrywa wyścig — sięga po sieć, zamontowany dysk albo bazę danych, zanim to w ogóle istnieje.

Naprawą jest zadeklarowanie zależności zamiast modlenia się:

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

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

After= ustala kolejność startu; Wants= sprawia, że systemd faktycznie podniesie zależność. network-online.target działa tylko wtedy, gdy usługa czekająca na sieć jest włączona w twojej dystrybucji — sprawdź systemctl is-enabled NetworkManager-wait-online.service (albo odpowiednik dla systemd-networkd). Strażnik ExecStartPre= to tani, uczciwy sposób, by paść głośno, z czytelnym komunikatem zamiast stack trace’a.

Drugi wariant: usługa startuje przy bootcie, ale natychmiast umiera. Szukaj rzeczy, które miało twoje interaktywne środowisko, a nie ma boot — różnice w PATH, virtualenv, HOME. Ustaw wprost to, czego potrzebujesz:

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

Czemu moja usługa nie loguje do journala?

Jeśli journalctl -u nie pokazuje nic, sprawdź trzy rzeczy po kolei:

  1. StandardOutput= i StandardError= w unit — muszą być journal (domyślne) albo journal+console. Ktoś mógł ustawić null.
  2. Aplikacja pisze do pliku zamiast na stdout. Systemd łapie tylko stdout/stderr; piszący do plików omijają journal zupełnie. Albo skieruj aplikację na stdout, albo czytaj plik.
  3. Limity magazynu wycięły stare linie: journalctl --disk-usage, a SystemMaxUse= w /etc/systemd/journald.conf, jeśli journal się dławi.

Do debugowania samego startu niewiele bije wpięcie shella w kontekst bootowy:

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

Albo, przy prawdziwych zagadkach, odpal dokładną komendę ExecStart= jako użytkownik unitu w shellu — większość różnic środowiskowych wychodzi w pierwsze dziesięć sekund.

Jak wymusić automatyczny restart po crashu?

Domyślna polityka to Restart=no: usługa po crashu zostaje martwa, a ty dowiadujesz się od użytkownika. Napraw to per usługa:

[Service]
Restart=on-failure
RestartSec=5
UstawienieRestartuje, gdy
no (domyślne)Nigdy
on-failureNiezerowy kod wyjścia, sygnał, timeout
alwaysKażde wyjście, nawet czyste
on-watchdogTylko timeout watchdogga

Paruj Restart=on-failure z zabezpieczeniem na tempo startów, żeby zapętlający się crash nie kłuł maszyny: StartLimitIntervalSec= i StartLimitBurst= w sekcji [Unit]. Pięć awarii na sześćdziesiąt sekund powinno budzić człowieka, nie kręcić CPU.

Jeśli składasz to jako zadanie harmonogramowane, a nie demona, najpierw zważ cron vs timer systemd — timery dają logowanie do journala i porządek zależności w pakiecie, czyli dokładnie to, po co ten artykuł wciąż sięga.

Checklista na 60 sekund

  1. systemctl status <unit> — przeczytaj stan i ostatnie linie.
  2. journalctl -u <unit> -n 50 --no-pager — znajdź prawdziwy błąd.
  3. 203/EXEC? Napraw ścieżkę, shebang, uprawnienia. Permission denied? Napraw użytkownika i właściciela plików.
  4. Pada tylko przy bootcie? Dołóż After=/Wants=network-online.target i strażnika ExecStartPre=.
  5. Zmieniłeś plik unit? systemctl daemon-reload && systemctl restart <unit>.
  6. Dołóż Restart=on-failure, żeby następny crash sam się zgłosił, zamiast się chować.

Większość momentów „systemd jest skomplikowany” redukuje się do błąd cały czas był w journalu. Czytaj go przed edycją pliku unit, nie po.

— mrsaynothing

Get the next one by email

One email per post. No spam, no algorithms.

self-hosted · no third parties · one-click unsubscribe

what is this?

Kwantyzacja GGUF: jaki poziom wybrać?

Podobają się teksty? Tak buduję zawodowo. zatrudnij mnie