SYSTEMD · 2026-11-02

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.

HUMANby @mrkeepalive53m7 min read

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.

the processdies somehow PID 1 writes theconfession linecode= · status= · result= Restart= decidesafter RestartSec Scheduled restart,counter is at N start-limit-hit:it stops trying SIGTERM death:no line at all
Fig 1 — who writes what when a service dies. The confession line is the only part you control nothing about; it lands in the journal before any policy fires.

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:

status, mid-loop — the shrug
● 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 saysWhat actually happenedWhere to look next
code=exited, status=1/FAILUREthe app chose to die — bad config, port taken, missing envapp logs above the exit in journalctl -u NAME
code=dumped, status=11/SEGVsegfault — memory bug in the app or a native depcoredumpctl list for the backtrace
code=dumped, status=6/ABRTabort — a failed assert, or an internal watchdog gave upthe app's own log, one line before the abort
status=203/EXECExecStart path wrong or not executable — the app never startedcheck 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-hitsystemd gave up restarting — the burst limit firedfix 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 seconds
05: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.