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.service does not imply Requires=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

DirectivePulls the listed unit in?If it fails to startIf it is stopped later
Wants=YesThis unit starts anywayNo effect
Requires=YesThis unit fails, only if After= is also setThis unit is stopped or restarted
Requisite=NoFails immediately unless the unit is already activeNo effect
BindsTo=YesThis unit failsThis 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=A means this unit finishes starting before A begins starting.
  • After=A means 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= a Type=simple unit buys you nothing beyond fork().

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=yes adds After= on the /tmp mounts and on systemd-tmpfiles-setup.service.
  • StateDirectory=, RuntimeDirectory=, CacheDirectory=, LogsDirectory=, ConfigurationDirectory= and WorkingDirectory= add Requires= plus After= on the mounts covering those paths.
  • Type=dbus adds a dependency on dbus.socket.
  • RootImage= adds After=systemd-udevd.service.
  • A .timer with OnCalendar= gains After=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 Condition for Assert and 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.