Units, dependencies & targets

How systemd orders and wires the system.

Advanced14 min · lesson 5 of 17

systemd is far more than a service starter — it is the manager that models the whole system as a graph of units with dependencies, and understanding that graph is what lets you reason about boot order, failures, and cleanups. A unit is any managed thing: a .service (a process), a .socket (a listening socket), a .timer (a schedule), a .mount, a .target (a grouping/milestone). Each declares how it relates to others, and systemd computes the order.

TWO INDEPENDENT AXES: ORDERING vs REQUIREMENT
Ordering — WHEN it starts (not whether)
After=
start after the other unit
Before=
start before the other unit
Requirement — WHETHER it's pulled in (not when)
Wants=
soft: pull in, don't fail if it fails
Requires=
strict: fail if the dependency fails
You usually want both: After=postgresql AND Wants=postgresql. After= alone orders but never starts the dependency.
terminal
$ systemctl list-units --type=service # running services
$ systemctl cat nginx.service # the actual unit definition
$ systemctl show nginx -p After -p Requires # what it is ordered after / needs
After=network.target ...
Requires=...

Ordering vs requirement

Two independent relationships trip people up. Ordering (After=, Before=) says when a unit starts relative to another — but not whether it needs it. Requirement (Requires=, Wants=) says whether starting this unit should pull in another — but not in what order. You usually want both: a web app is After=postgresql AND Wants=postgresql, so the database is started and started first. Wants is the softer form (pull it in, but do not fail if it fails); Requires is strict. Getting these right is how you express real service dependencies.

myapp.service
[Unit]
Description=My App
Wants=postgresql.service # pull in the DB (soft: do not fail if it does)
After=postgresql.service network-online.target # ...and start after it
[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure

Targets: milestones, not runlevels

Targets replace the old SysV runlevels with named grouping units — multi-user.target (a normal server), graphical.target (with a GUI), rescue.target (single-user recovery). A target is just a well-known synchronization point that pulls in a set of units; "boot to multi-user" means "reach that target," which means "start everything wanted by it." You can list what a target pulls in, and switch targets at runtime, which is how you drop to rescue mode or bring the system to a known state.

terminal
$ systemctl get-default
multi-user.target # what the system boots to
$ systemctl list-dependencies multi-user.target | head # what it pulls in
$ sudo systemctl isolate rescue.target # switch to single-user recovery now
After= does not imply Requires=
The most common systemd dependency bug: writing After=postgresql on a web app and expecting the database to be started for you. After= only controls ordering IF postgresql is being started — it does not cause it to start. If your app needs the database up, you must also declare Wants= or Requires=. Two units can be correctly ordered and yet the dependency never starts, leaving your service to fail at boot for a reason that looks mysterious until you know these are separate axes.