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:

  • terminate sends KillSignal= (SIGTERM by default). If the process still has not exited after TimeoutStopSec=, systemd sends FinalKillSignal= (SIGKILL by default).
  • abort sends WatchdogSignal= (SIGABRT by default) instead, waits for TimeoutAbortSec=, then sends the final kill signal. It is useful when you want a core dump from a service that hangs during startup.
  • kill sends 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:

  1. It bounds each ExecStop= command. If one times out, the remaining ExecStop= commands are skipped and the service is terminated with SIGTERM.
  2. 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 a Type=simple service and call it fixed. The clock is not measuring initialization.
  • Prefer Type=notify when the application can say when it is ready. Prefer Type=exec over simple even 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 status looks healthy but the start never returns, look at ordering and systemctl list-jobs, not at TimeoutStartSec=.

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