systemd show reason for service restart
Show the reason a systemd service restarted: status shrugs, the journal confesses. Five death codes from a live bench — and the one death it calls success.
We killed one service five different ways on a throwaway Arch VM and let systemd interrogate itself. Every line below is the journal's own output, printed unedited — four confessions, one silence, and one "gave up" that status output never mentions. If you came here from our earlier autopsy of services that refuse to start, this is the room after the door slams: the service started, ran, died, and you want to know who did it.
How do I show the reason a systemd service restarted?
Three commands, in this order. The first reads the tape, the second reads the counter, the third filters the tape to just the deaths.
# the confession, most recent first
journalctl -u myservice.service -n 50 --no-pager
# the counter and the last death, machine-readable
systemctl show myservice.service -p NRestarts -p ExecMainStatus \
-p ExecMainCode -p Result
# every death, timestamped to the millisecond
journalctl -u myservice.service --grep 'Main process exited' \
-o short-precise | tail -10
Verify: on our bench VM, the second command answered Result=exit-code, NRestarts=3, ExecMainStatus=1, ExecMainCode=1 for a service that had died three times seconds before. Last tested 2026-11-02.
The one most people miss is NRestarts. systemctl status resets its story on every restart, so it always looks like the service "just started". The counter does not reset. On the bench, status reported active (running), 10 ms old — while the counter one line below said restart counter is at 3. That gap is the whole debugging session: the service has a criminal record, and status shows its birth certificate.
Why does systemctl status not show why the service restarted?
Because status is a photo of right now, and the journal is the CCTV tape. Here is the photo, printed from the bench while the service was dying every ~1.2 seconds:
● flaky.service - dies on purpose for the journal
Loaded: loaded (/etc/systemd/system/flaky.service; static)
Active: active (running) since Mon 2026-11-02 05:05:46 UTC; 10ms ago
Main PID: 627 ((die.sh))
CPU: 3ms
Nov 02 05:05:46 bench-arch systemd[1]: flaky.service: Scheduled restart
job, restart counter is at 3.
status is a photo of now; the journal is the CCTV tape.
What do code=exited and status=11/SEGV mean in the journal?
Every death produces two journal lines — the code line and the verdict. We triggered five kinds on purpose. These are the confession lines, unedited:
the confession gallery — one per death, straight from the bench# death 1: the app exits nonzero
flaky.service: Main process exited, code=exited, status=1/FAILURE
flaky.service: Failed with result 'exit-code'.
# death 2: the app segfaults (kill -SEGV)
flaky.service: Main process exited, code=dumped, status=11/SEGV
# death 3: the app aborts (kill -ABRT) — assert died, or a
# watchdog inside the app pulled the plug
flaky.service: Main process exited, code=dumped, status=6/ABRT
# death 4: ExecStart points at a file that does not exist —
# the app never ran
(nothere)[1260]: flaky.service: Failed at step EXEC spawning
/usr/local/bin/nothere: No such file or directory
flaky.service: Main process exited, code=exited, status=203/EXEC
# death 5: the app kills itself with SIGTERM (kill -TERM)
flaky.service: Deactivated successfully.
Decode rule: code= tells you how it left (exited = walked out, dumped = carried out), status= names the manner of death, and Failed with result is systemd's one-line summary. The table is the full decoder we ran into:
Journal says What actually happened Where to look next
code=exited, status=1/FAILURE the app chose to die — bad config, port taken, missing env app logs above the exit in journalctl -u NAME
code=dumped, status=11/SEGV segfault — memory bug in the app or a native dep coredumpctl list for the backtrace
code=dumped, status=6/ABRT abort — a failed assert, or an internal watchdog gave up the app's own log, one line before the abort
status=203/EXEC ExecStart path wrong or not executable — the app never started check the path, chmod +x, interpreter shebang
Deactivated successfully (no failure line) died by SIGTERM — counted as a clean exit by default § below; this is the phantom
start request repeated too quickly / start-limit-hit systemd gave up restarting — the burst limit fired fix the death first; only then touch the limit
Why does my systemd service keep restarting every few seconds?
Because Restart= is doing its job and the death never stopped. The cadence is set by RestartSec (default 100 ms) and the patience by StartLimitIntervalSec/StartLimitBurst (default 5 failures in 10 s). On the bench, with RestartSec=1, three deaths landed like this:
cadence — RestartSec=1, three deaths in three seconds05:05:43 flaky.service: Scheduled restart job, restart counter is at 1.
05:05:44 flaky.service: Scheduled restart job, restart counter is at 2.
05:05:46 flaky.service: Scheduled restart job, restart counter is at 3.
# and when the burst limit applies, the loop ends like this:
flaky.service: Start request repeated too quickly.
flaky.service: Failed with result 'start-limit-hit'.
Count your own loop's rate in one line:
# restarts in the last hour, with timestamps
journalctl -u myservice.service --since "1 hour ago" \
--grep 'Scheduled restart' --no-pager | tail -20
Verify: on the bench this printed the three lines above; the counter reset to 1 once the 10 s limit window rolled over. Last tested 2026-11-02.
The muzzle trap deserves its own warning: raising StartLimitBurst does not fix a dying service, it hides the body more often. Fix the death — the decoder table above names where to look — and treat the limit as the smoke alarm it is.
Why did my service stop, and the journal says success?
This is the one we did not expect, and it is the reason the post exists. Death 5 was the process killing itself with SIGTERM. No restart followed. No failure line appeared. The journal's entire comment was Deactivated successfully, and systemctl show answered Result=success, ExecMainCode=0, ExecMainStatus=0 — a murder reported as a clean retirement.
The cause is in the man page, not the journal: the default SuccessExitStatus list includes SIGTERM (and SIGINT, SIGPIPE, status 0). Any process that dies by SIGTERM is, as far as restart policy cares, a success — so Restart=on-failure never fires, and nothing in the log calls it a death. If your service is supervised or gets terminated by an external watchdog, this is the silence you have been staring at.
Two honest ways out, depending on what you want:
The bench method, so you can run the five deaths yourself
Throwaway Arch VM (quickemu, 2 vCPU, no host changes). One unit, one script: /usr/local/bin/die.sh takes a death kind as argv — exit 1, kill -SEGV $$, kill -ABRT $$, kill -TERM $$, or an ExecStart pointed at a nonexistent path for the 203. Swap the unit's ExecStart between deaths, daemon-reload, start, let it die three times, stop, capture. Full unedited receipt log: five confessions, the shrug, the cadence, the give-up, and the phantom — the journal lines printed above are selected verbatim from it. CPU-shaped box, nothing GPU-adjacent, nothing preserved: clone destroyed the same session.
The honest ledger: we did not decode coredumps (coredumpctl is its own evening), and the bench names are fake — your journalctl lines will name your units. What held: four of five deaths confess with code=, status= and a result line, the counter never forgets, and the fifth death — the SIGTERM — the journal calls a success. The journal's silence is itself a confession. status will keep shrugging; read the tape.