systemd Cheatsheet
Dependencies and Ordering
Use this systemd reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Ordering Is Not Requirement
systemd has two completely separate axes, and conflating them is the classic mistake.
- Ordering (
After=,Before=) decides when things run relative to each other. It pulls in nothing. - Requirement (
Requires=,Wants=,BindsTo=, ...) decides what else gets started. It says nothing about order.
After=postgresql.servicedoes not implyRequires=postgresql.service. If Postgres is not already being started by something else, your unit starts with nothing to wait for and comes up before the database exists.
Say both, every time:
[Unit] Wants=postgresql.service After=postgresql.service
The reverse trap is equally real. Requires= without After= starts both units in parallel, so your service can still race ahead of its dependency. A requirement alone buys you existence, not sequencing.
Requirement Directives
| Directive | Pulls the listed unit in? | If it fails to start | If it is stopped later |
|---|---|---|---|
Wants= | Yes | This unit starts anyway | No effect |
Requires= | Yes | This unit fails, only if After= is also set | This unit is stopped or restarted |
Requisite= | No | Fails immediately unless the unit is already active | No effect |
BindsTo= | Yes | This unit fails | This unit is stopped, even if the other died on its own |
PartOf= | No | — | Stop or restart propagates from the listed unit to this one, one way only |
Upholds= | Yes | — | Continuously restarted while this unit is up (v249+) |
Conflicts= | Stops it | — | Starting either stops the other |
OnFailure= | On failure only | — | Activated when this unit enters failed |
OnSuccess= | On success only | — | Activated when this unit goes inactive (v249+) |
Pick Wants= unless you can explain why not. Requires= makes a system that cascades failures. Requires= also does not mean the other unit must stay active, since some unit types deactivate on their own.
BindsTo= is the strong form: with After= it guarantees this unit is never active while the other is not. Use it for a service that is meaningless without a specific device or mount.
PartOf= is the group-restart idiom. Put PartOf=web.target on each worker and systemctl restart web.target restarts them all, while restarting one worker leaves the target alone.
Conflicts= does not imply ordering either. Add After= or Before= so the stop job lands before the start job.
Ordering Directives
[Unit] After=network-online.target nss-lookup.target Before=nginx.service
Before=Ameans this unit finishes starting before A begins starting.After=Ameans A is fully up before this unit begins.- On shutdown the order is inverted automatically.
- When one unit is stopping and another starting, the stop is always ordered first, regardless of which direction the dependency points.
- "Fully started" is defined by the other unit's
Type=.After=aType=simpleunit buys you nothing beyondfork().
Both accept multiple space-separated names and may be repeated. Prefer network-online.target (with a matching Wants=) over network.target when you need a routable address, because network.target only means the networking stack was brought up.
Implicit and Default Dependencies
Most units silently gain dependencies you did not write.
[Unit] DefaultDependencies=no
Without that line, a service gets Requires=sysinit.target, After=sysinit.target basic.target, and Conflicts=shutdown.target plus Before=shutdown.target. Turning it off is for very early boot units only, and it will break ordinary services.
Directives that add dependencies behind your back:
PrivateTmp=yesaddsAfter=on the/tmpmounts and onsystemd-tmpfiles-setup.service.StateDirectory=,RuntimeDirectory=,CacheDirectory=,LogsDirectory=,ConfigurationDirectory=andWorkingDirectory=addRequires=plusAfter=on the mounts covering those paths.Type=dbusadds a dependency ondbus.socket.RootImage=addsAfter=systemd-udevd.service.- A
.timerwithOnCalendar=gainsAfter=time-set.target time-sync.target.
RequiresMountsFor=/srv/data is the explicit way to wait for an arbitrary path's mount.
Conditions and Asserts
Conditions are checked at start time. A failed condition skips the unit and reports success, which is very different from a failure.
[Unit] ConditionPathExists=/etc/myapp/enabled ConditionPathIsDirectory=/srv/data ConditionFileNotEmpty=/etc/myapp/config.toml ConditionDirectoryNotEmpty=/srv/data/queue ConditionKernelCommandLine=!myapp.disable ConditionVirtualization=!container ConditionHost=web1 ConditionFirstBoot=yes ConditionACPower=yes ConditionMemory=>=2G ConditionUser=root
- Prefix with
!to negate. - An empty assignment (
ConditionPathExists=) resets the whole list, drop-in style. - Swap
ConditionforAssertand a failure becomes a real, loud failure instead of a skip. - A failed condition does not make a
Requires=on that unit fail.
systemd-analyze condition 'ConditionPathExists=/etc/hosts' \ 'ConditionVirtualization=!container'
Inspecting the Real Graph
systemctl list-dependencies nginx # what nginx pulls in systemctl list-dependencies --reverse nginx # who pulls in nginx systemctl list-dependencies --after nginx # ordering, what runs before systemctl list-dependencies --before nginx systemctl show -p After -p Before -p Wants -p Requires nginx systemctl show -p WantedBy -p RequiredBy -p ConsistsOf nginx systemd-analyze critical-chain nginx.service systemd-analyze verify nginx.service # reports ordering cycles
systemctl show prints the resolved dependency set, including everything implicit, which is the only view that tells you the truth. PartOf=b.service on a.service shows up as ConsistsOf=a.service on b.service, and ConsistsOf= cannot be set directly.
Ordering cycles are broken by systemd deleting one job at random and logging "Found ordering cycle". Fix the loop, do not ignore the warning.