skip to content

`systemctl start myapp.service` returns immediately and `systemctl status` reports active (running), but the application does not accept connections for another twenty seconds. What does "active" actually mean under the default Type=simple, and how do you make systemd wait for real readiness?

level: middleimportance: should knowfreq 52%

answer

  1. active is about the job, not the app
  2. forked is not the same as serving
  3. the service can report its own readiness
  4. READY=1 over a socket
  5. a slow starter sits in activating

basics

~20 s

Under Type=simple, systemd calls a unit active as soon as it has forked the main process — it never asks whether the program is ready, or even whether the binary exists. Use Type=exec to wait for a successful exec, or Type=notify so the service reports READY=1 itself.

solid answer

~40 s

With `Type=simple`, which is the default when a unit has `ExecStart=` and no explicit `Type=`, systemd considers the service started the instant it has forked the process. It does not wait for the program to exec successfully, bind a port, or finish warming up, so `active (running)` means "a process was spawned", not "the service works". `Type=exec` narrows that slightly: systemd waits until `execve()` has succeeded, so a missing binary or a bad permission shows up as a start failure instead of an instant crash. Real readiness needs `Type=notify`: the service calls `sd_notify()` with `READY=1` once it is genuinely serving, and systemd holds the start job open until then, or fails the unit when `TimeoutStartSec=` (90s by default) expires. Shell-based units can call `systemd-notify --ready` instead.

code

ini · 11 lines
ini
[Unit]
Description=Example API

[Service]
Type=notify
NotifyAccess=main
TimeoutStartSec=120s
ExecStart=/usr/local/bin/exampled --listen :8080

[Install]
WantedBy=multi-user.target

go deeper

for a junior

Know that a unit reporting active only means systemd started a process, and that the application can still be warming up or already broken.

for a middle

Explain what each Type= value makes systemd wait for — fork, exec, exit, PID file, or a READY=1 notification — and name TimeoutStartSec= as the bound on that wait.

for a senior

Diagnose a unit stuck in activating, and argue for adding sd_notify to services you own so that systemd's start state actually matches serving state rather than process existence.

for a principal

Decide fleet-wide whether readiness is a systemd concern or belongs to a health-check layer, and set the expectation for in-house services so ordering and rollout tooling can trust the unit state.

## What "active" is a statement about A systemd unit's active state is a statement about the *job* systemd ran, not about the health of your application. `Type=` in the `[Service]` section is precisely the knob that decides when systemd declares the start job complete, and the default is the loosest possible answer. ## Type=simple: forked is started ```ini [Service] ExecStart=/usr/local/bin/exampled --listen :8080 ``` With no `Type=` and an `ExecStart=`, this is `Type=simple`. systemd forks, arranges the child's environment, and immediately reports the unit as `active (running)`. The child may then fail to `execve()` because the path is wrong, or exec fine and spend twenty seconds loading a model into memory, or bind nothing at all — systemd cannot tell the difference, because it never asked. This produces two familiar symptoms. First, a typo in `ExecStart=` shows up as a unit that is active for a few milliseconds and then failed, rather than a start command that returns an error. Second, anything scheduled to follow this unit begins while it is still warming up. ## Type=exec: wait for the exec to succeed `Type=exec` behaves like `simple` except that systemd holds the start job until the child has successfully executed the binary. A missing file, a non-executable bit, or a bad interpreter now fails the start job itself, so `systemctl start` returns non-zero and says so. It still tells you nothing about readiness — exec succeeding is not the application working — but it removes a whole class of confusing "it said it started" reports. `Type=exec` requires systemd 240 or newer. ## Type=notify: the service says when it is ready ```ini [Service] Type=notify NotifyAccess=main ExecStart=/usr/local/bin/exampled TimeoutStartSec=120s ``` Under `Type=notify`, systemd passes a Unix datagram socket to the service through the `NOTIFY_SOCKET` environment variable and waits. The service finishes its own startup — opening its database pool, loading configuration, binding its listener — and then sends a readiness datagram: ```c sd_notify(0, "READY=1"); ``` Only at that point does the unit become `active (running)`. If the message never arrives, the start job hangs until `TimeoutStartSec=` (90 seconds by default) elapses and the unit is marked failed with a timeout. `NotifyAccess=` controls which processes may send the message; `main` is the default and is what you want unless a helper process must do the notifying. A program does not need the C library binding: any process can write to `$NOTIFY_SOCKET`, and shell units can simply run `systemd-notify --ready`. The same channel also carries `STATUS=` strings, which then appear in `systemctl status` output, and `RELOADING=1` for reload progress. ## The other types you will be asked to distinguish - `Type=oneshot` — systemd waits for the process to *exit* before considering the unit started. This is the type for a job that does one thing and finishes. Paired with `RemainAfterExit=yes` it produces the state that confuses everyone: `active (exited)`, meaning "the job ran successfully and there is deliberately no process". Without `RemainAfterExit=`, a successful oneshot ends `inactive (dead)`. - `Type=forking` — for daemons that background themselves; systemd waits for the initial process to exit and treats a surviving child as the service, usually assisted by `PIDFile=`. - `Type=dbus` — the unit is ready when the name in `BusName=` appears on the bus. - `Type=idle` — like simple, but delays execution slightly so console output does not interleave with other startup messages. ## Reading the state names `systemctl status` and `systemctl is-active` speak a small vocabulary that pays to know exactly: `activating` (the start job is still running — for `Type=notify` this is where a slow starter sits), `active (running)` (a process exists), `active (exited)` (a oneshot completed and `RemainAfterExit=yes` is set), `deactivating`, `inactive (dead)`, and `failed`. A unit stuck in `activating (start)` is almost always a `Type=notify` service that never sent `READY=1`, or a `Type=forking` unit whose PID file never appeared. ## The practical fix for the reported symptom If you own the code, implement `sd_notify(0, "READY=1")` and switch the unit to `Type=notify` — this is the only mechanism that makes systemd's notion of "started" match your application's. If you do not own it, `Type=exec` at least makes exec failures honest, and the real readiness signal has to come from whatever consumes the service rather than from the unit state.

  • A unit is stuck in activating (start) and eventually fails with a timeout. What are the two usual causes?
    Either it is `Type=notify` and the service never sent `READY=1` — often because the code path was never added, or `NotifyAccess=` excludes the process that sends it — or it is `Type=forking` and the expected PID file never appeared where `PIDFile=` says. Both make systemd wait out `TimeoutStartSec=`, 90 seconds by default, before marking the unit failed.
  • What does the state active (exited) mean, and which directives produce it?
    It means the unit ran to completion successfully and deliberately leaves no process behind. It comes from `Type=oneshot` together with `RemainAfterExit=yes`, which tells systemd to keep considering the unit active after the process is gone — typical for units that apply a configuration or set up a device. Without `RemainAfterExit=`, the same job ends inactive (dead).
  • Can a service report readiness without linking against libsystemd?
    Yes. systemd passes a Unix datagram socket path in the `NOTIFY_SOCKET` environment variable, and any process can write `READY=1` to it — there is nothing privileged or library-specific about the protocol. Shell units usually just call `systemd-notify --ready`, and most languages have a small third-party implementation of the same few lines.

saying these in an interview costs you the question

  • Thinks active (running) means the app is serving traffic
  • Assumes Type=simple waits for a successful exec
  • Believes systemd probes a port to decide readiness
  • Says Type=notify works without the service sending anything
  • Confuses active (exited) with a crashed service

context