A deploy stalls. You run systemctl restart payments.service, wait, and then get a line about the start operation timing out. The process did not crash. It ran out of a clock you may not have known was ticking.
systemd's 90-second default is not one timeout. It is a family of clocks that bound different phases of a unit's life, and they compose in ways that surprise people. This post maps them, shows what each one actually measures, and ends with drop-ins you can copy.
The values below were checked on Debian 13 with systemd 257.13. The core semantics have been stable for years; options added later are called out.
The default really is 90 seconds, twice
DefaultTimeoutStartSec= and DefaultTimeoutStopSec= default to 90 seconds in both the system manager and the per-user manager, per systemd-system.conf(5). A unit inherits those unless it sets TimeoutStartSec=, TimeoutStopSec=, or the shorthand TimeoutSec=, which sets both.
You can read the effective values for any unit without guessing:
$ systemctl show -p TimeoutStartUSec -p TimeoutStopUSec -p TimeoutAbortUSec ssh.service TimeoutStartUSec=1min 30s TimeoutStopUSec=1min 30s TimeoutAbortUSec=1min 30s The property names end in USec because systemd stores these as microseconds, but the values are the ordinary 90-second defaults. TimeoutAbortUSec is also 90 seconds here because DefaultTimeoutAbortSec= is unset by default and falls back to TimeoutStopSec=.
Disabled timeouts print as infinity, which is a useful thing to check when a stop seems to hang forever.
The start timeout does not mean what you think
TimeoutStartSec= bounds the interval during which systemd waits for a service to signal that start-up completed. What counts as "completed" depends entirely on Type=:
| Type | Start-up is considered complete when | What the timeout really bounds |
|---|---|---|
simple | The program has begun executing | Almost nothing after fork/exec |
exec | The binary was executed successfully | Missing binary, bad user, permission errors |
forking | The parent process exits | Daemonization, not application readiness |
notify | The service sends READY=1 | Actual initialization |
oneshot | ExecStart= exits | The whole job; disabled by default |
That first row is the trap. For Type=simple, systemd assumes the unit is started immediately after the program begins executing. A Python service that then spends ten minutes loading a model is already "started" as far as the manager is concerned. Raising TimeoutStartSec= on a simple unit will not make a slow initialization succeed, because the clock stopped long before the slow part began.
If initialization matters, make it observable. Use Type=notify and have the application send READY=1 through sd_notify(3) when it can actually serve traffic. A notify service can also request more time with EXTEND_TIMEOUT_USEC=, but the first extension has to arrive before the original deadline; it is an extension, not a reset.
For Type=oneshot, the start timeout is disabled by default. That is deliberate: batch jobs can legitimately run for hours. If you want a ceiling, set one explicitly.
One more detail that catches people: the start timeout also applies to reloads, whether the reload runs through ExecReload= or through the Type=notify-reload logic. A slow reload can fail with the same timeout as a slow start.
What happens at the deadline
When the start timeout expires, TimeoutStartFailureMode= decides the response. It defaults to terminate, and the alternatives are abort and kill:
-
terminatesendsKillSignal=(SIGTERM by default). If the process still has not exited afterTimeoutStopSec=, systemd sendsFinalKillSignal=(SIGKILL by default). -
abortsendsWatchdogSignal=(SIGABRT by default) instead, waits forTimeoutAbortSec=, then sends the final kill signal. It is useful when you want a core dump from a service that hangs during startup. -
killsends the final kill signal immediately, with no grace period. It is for services you already know will not clean up.
This is why a "90-second timeout" can occupy closer to three minutes: 90 seconds waiting for readiness, then up to another 90 seconds of grace before SIGKILL.
The failure modes were added in systemd 246, so very old units will not have them.
The stop timeout is two clocks in one setting
TimeoutStopSec= does double duty, and the documentation is explicit about both roles:
- It bounds each
ExecStop=command. If one times out, the remainingExecStop=commands are skipped and the service is terminated with SIGTERM. - It bounds the service's own shutdown after that signal. If the process is still alive at the end, systemd sends SIGKILL, subject to
KillMode=.
So a unit with an ExecStop= script and a process that ignores SIGTERM can consume up to two full stop timeouts: one for the script, one for the process. With the defaults, that is up to 180 seconds.
KillMode= controls who receives the signals. The default, control-group, sends SIGTERM to every process in the unit's cgroup and SIGKILL to whatever remains. mixed sends SIGTERM only to the main process while still SIGKILLing the rest after the deadline. That is often the right choice for a supervisor that needs to shut its children down in order.
RuntimeMaxSec is a different limit
RuntimeMaxSec= caps how long a service may remain active, not how long it may take to start. When the limit is reached, the service is terminated and the unit enters a failure state. The default is infinity.
It does not apply to Type=oneshot services, because those are considered done as soon as ExecStart= exits. For a batch job, use TimeoutStartSec=; for a daemon that must recycle after a fixed interval, use RuntimeMaxSec=.
The fourth clock: job timeouts
There is a separate layer above services. JobTimeoutSec= starts when a job is queued; JobRunningTimeoutSec= starts when the queued job actually begins running. Both default to infinity and live in the [Unit] section.
If either limit is reached, systemd cancels the job, but the unit does not change state and does not enter failed mode. That explains the confusing case where systemctl status looks normal while the start command reported a job timeout. The usual cause is ordering: the unit is waiting behind a mount, a device, or another unit that never becomes ready.
Useful commands when that happens:
systemctl list-jobs systemctl show -p JobTimeoutUSec -p JobRunningTimeoutUSec payments.service systemd-analyze critical-chain payments.service For mounts, x-systemd.device-timeout= in /etc/fstab bounds the device wait separately, and DefaultDeviceTimeoutSec= defaults to 90 seconds.
Drop-ins that match reality
A slow-starting API that can signal readiness:
# /etc/systemd/system/payments.service.d/timeouts.conf [Service] Type=notify TimeoutStartSec=5min TimeoutStopSec=2min TimeoutStartFailureMode=terminate TimeoutStopFailureMode=abort A nightly import that must be allowed to finish:
# /etc/systemd/system/nightly-import.service.d/timeouts.conf [Service] Type=oneshot TimeoutStartSec=2h A service with a slow, well-behaved shutdown:
# /etc/systemd/system/search.service.d/timeouts.conf [Service] TimeoutStopSec=3min KillMode=mixed Apply and verify:
sudo systemctl daemon-reload systemctl show -p TimeoutStartUSec -p TimeoutStopUSec -p TimeoutAbortUSec payments.service For the first example, expect 5min, 2min, and 2min: the abort timeout falls back to the stop timeout because DefaultTimeoutAbortSec= is unset. That is a quick way to confirm the drop-in was actually read.
A short diagnostic sequence
When a unit times out and you are not sure which clock fired:
systemctl status payments.service systemctl show -p Result -p ActiveState -p SubState \ -p TimeoutStartUSec -p TimeoutStopUSec payments.service journalctl -u payments.service -b --no-pager | tail -40 systemctl list-jobs systemd-analyze verify /etc/systemd/system/payments.service Result=timeout points at a start or stop deadline. A normal unit state plus a failed start command points at a job timeout. A dead unit with no useful journal lines often means the process died before it could log, which is a different problem.
After fixing the unit, clear the failed state with systemctl reset-failed payments.service.
Rules of thumb
- Do not raise
TimeoutStartSec=on aType=simpleservice and call it fixed. The clock is not measuring initialization. - Prefer
Type=notifywhen the application can say when it is ready. PreferType=execoversimpleeven when it cannot, because at least exec failures are detected. - Set the start timeout from the slowest documented startup step, plus a margin. A timeout that is too generous hides hangs; one that is too tight causes avoidable restarts.
- Keep the stop timeout below whatever your deployment tool waits for before it gives up. A service that takes 180 seconds to stop while the deploy waits 60 is a service that gets killed by the deploy anyway.
- Remember that job timeouts are independent. If
systemctl statuslooks healthy but the start never returns, look at ordering andsystemctl list-jobs, not atTimeoutStartSec=.
The 90-second default is reasonable for a simple daemon. It is a bad fit for a database, a model server, or a migration job. The fix is not to disable timeouts globally; it is to choose the clock that matches the phase you actually care about.
References
- systemd.service(5) —
Type=,TimeoutStartSec=,TimeoutStopSec=, failure modes,RuntimeMaxSec= - systemd-system.conf(5) —
DefaultTimeoutStartSec=,DefaultTimeoutStopSec=,DefaultTimeoutAbortSec= - systemd.unit(5) —
JobTimeoutSec=,JobRunningTimeoutSec= - systemd.kill(5) —
KillMode=,KillSignal=,FinalKillSignal=,WatchdogSignal= - systemctl(1) and journalctl(1) — inspecting effective values and logs



