Das Problem
Bootet ein Linux-Rechner langsam, listet systemd-analyze blame die Units
nach Startdauer, und systemd-analyze critical-chain zeigt, was worauf
gewartet hat. Für das Herunterfahren gibt es keinen solchen Befehl. Ein
langsamer Shutdown zeigt sich meist als Meldung "A stop job is running for ..."
mit einem Zähler bis 90 Sekunden. Der übliche Rat ist, mit einer Debug-Shell zu
booten oder einen Shutdown-Hook einzurichten und dann das rohe Log zu lesen.
Der Bedarf ist belegt:
- systemd/systemd#3371, "Provide systemd-analyze blame equivalent for system shutdown", ist seit Mai 2016 offen und hat 58 Reaktionen.
- Drei Fragen zu "A stop job is running" (1, 2, 3) haben zusammen etwa 400.000 Aufrufe.
Wie es funktioniert
systemd schreibt die nötigen Daten bereits ins Journal. Jeder Stop-Job bekommt einen Eintrag "Stopping ..." und einen Eintrag "Stopped ..." mit Job-ID, Unit-Name und Zeitstempel in Mikrosekunden. Läuft eine Unit in ihren Stop-Timeout, protokolliert systemd das ebenfalls, gefolgt von einem Eintrag für jeden Prozess, den es beendet. Ist das Journal persistent, liegt all das nach dem Neustart noch auf der Platte.
shutdown-blame liest diese
strukturierten Felder mit journalctl -o json für den vorherigen Boot. Es
findet den Beginn des Shutdowns (die logind-Meldung "System is powering down",
ein Signal an PID 1 wie SIGRTMIN+3 oder ersatzweise den ersten Stop-Job nach dem
letzten Start-Job), ordnet jeden Stop-Job über seine Job-ID zu und gibt die
Jobs nach Dauer sortiert aus. Units der User-Manager sind enthalten. Das deckt
den häufigen Fall "A stop job is running for User Manager for UID 1000" ab.
Das ist die Ausgabe für den unten beschriebenen Testcontainer, gestoppt mit
docker stop:
Shutdown of boot bcc6c0a68390 began 2026-11-01 05:06:51 UTC (Received SIGRTMIN+3.).
The journal stopped 4.353s later; anything after that (final unmounts, killing leftover processes, firmware) is not recorded.
TOOK STARTED UNIT NOTES
4.216s +0.014s ignores-term.service timed out in stop-sigterm; SIGKILL: sh, sleep
3.009s +0.021s slow-stop.service
2.220s +0.023s user@1500.service
2.207s +0.024s user 1500: user-slow.service timed out in stop-sigterm; SIGKILL: sh, sleep
Das Werkzeug ist eine einzelne Python-Datei ohne Abhängigkeiten. Man kann sie
also auf einen Rechner kopieren, der gerade langsam heruntergefahren ist.
--json gibt denselben Bericht maschinenlesbar aus.
Tests
Die Unit-Tests laufen gegen ein Journal, das bei einem echten Container-Shutdown aufgezeichnet wurde, und gegen konstruierte Fälle: zwei Shutdowns in einem Boot, ein Absturz ohne Shutdown, derselbe Unit-Name im System- und in einem User-Manager, ein fehlgeschlagenes Unmount und Meldungen, die kein gültiges UTF-8 sind.
Der End-to-End-Test bootet systemd in einem privilegierten Container mit drei
Diensten: Einer stoppt sofort, einer hat ein ExecStop von 3 Sekunden, und
einer ignoriert SIGTERM bei TimeoutStopSec=4. Ein Benutzer mit Linger
betreibt einen User-Dienst, der SIGTERM ebenfalls ignoriert. Der Container wird
zweimal heruntergefahren, einmal mit docker stop und einmal mit
systemctl poweroff, und der Bericht wird gegen die erwarteten Dauern,
Timeouts und beendeten Prozesse geprüft. Das CI führt das auf Ubuntu 24.04,
Debian 12 und Fedora 42 aus. Dabei fiel ein Unterschied auf: systemd 257 auf
Fedora beendet Units nach dem Timeout mit SIGABRT. Das Werkzeug meldet deshalb
das Signal, das es im Log findet.
Grenzen
- Das Journal muss persistent sein (
/var/log/journalmuss existieren). - journald stoppt kurz vor dem Ende des Shutdowns. Die letzten Unmounts, das abschließende Beenden übriger Prozesse und Verzögerungen der Firmware stehen nicht im Journal und erscheinen deshalb nicht im Bericht.
- Es listet Dauern. Eine Abhängigkeitskette wie bei
critical-chainberechnet es nicht. - Getestet ist es in Containern. Der Neustart eines physischen Rechners schreibt dieselben Journal-Einträge, das CI prüft das aber nicht.
Code und Installationsanleitung liegen unter github.com/Jwrede/shutdown-blame.