The problem
When a Linux machine boots slowly, systemd-analyze blame lists the units by
start time and systemd-analyze critical-chain shows what waited for what.
For shutdown there is no such command. A slow shutdown usually shows up as the
message "A stop job is running for ..." with a counter that runs to 90 seconds,
and the common advice is to boot with a debug shell or install a shutdown hook
and then read the raw log.
There is demand for something better:
- systemd/systemd#3371, "Provide systemd-analyze blame equivalent for system shutdown", has been open since May 2016 and has 58 reactions.
- Three questions about "A stop job is running" (1, 2, 3) have about 400,000 views together.
How it works
systemd already writes what is needed into the journal. Every stop job gets a "Stopping ..." and a "Stopped ..." entry with a job ID, a unit name and a microsecond timestamp. When a unit runs into its stop timeout, systemd logs that too, followed by one entry per process it kills. If the journal is persistent, all of this is still on disk after the reboot.
shutdown-blame reads these
structured fields with journalctl -o json for the previous boot. It finds the
start of the shutdown (the logind message "System is powering down", a signal
to PID 1 such as SIGRTMIN+3, or as a fallback the first stop job after the last
start job), pairs every stop job by job ID and prints them sorted by duration.
Units of user service managers are included, which covers the frequent case
"A stop job is running for User Manager for UID 1000".
This is the output for the test container described below, stopped with
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
It is a single Python file without dependencies, so it can be copied onto a
machine that has just shut down slowly. --json prints the same report in a
machine-readable form.
Testing
The unit tests run against a journal captured from a real container shutdown and against constructed cases: two shutdowns in one boot, a crash without shutdown, the same unit name in the system and a user manager, a failed unmount, and non-UTF-8 messages.
The end-to-end test boots systemd in a privileged container with three
services: one stops at once, one has a 3-second ExecStop, and one ignores
SIGTERM with TimeoutStopSec=4. A lingering user runs a user service that also
ignores SIGTERM. The container is shut down twice, once with docker stop and
once with systemctl poweroff, and the report is checked against the expected
durations, timeouts and killed processes. CI runs this on Ubuntu 24.04,
Debian 12 and Fedora 42. One difference turned up there: systemd 257 on
Fedora kills timed-out units with SIGABRT, so the tool reports the signal it
finds in the log.
Limitations
- The journal must be persistent (
/var/log/journalmust exist). - journald stops near the end of the shutdown. The final unmounts, the last killing of leftover processes and firmware delays are not in the journal, so the tool cannot show them.
- It lists durations. It does not compute a dependency chain like
critical-chain. - It has been tested in containers. A reboot of a physical machine writes the same journal entries, but CI does not test that.
The code and installation instructions are at github.com/Jwrede/shutdown-blame.