Skip to content

systemd Units, Dependencies, and Ordering

Scope

This document is the hard part people often fake their way through.

It covers:

  • unit search paths
  • unit naming
  • aliases and templates
  • requirement relationships
  • ordering relationships
  • implicit/default dependencies
  • conflicts and isolation
  • targets
  • how transactions are built
  • how to reason about broken graphs

Reference anchors: - https://www.freedesktop.org/software/systemd/man/systemd.unit.html - https://www.freedesktop.org/software/systemd/man/systemd.service.html - https://www.freedesktop.org/software/systemd/man/systemd.special.html - https://www.freedesktop.org/software/systemd/man/systemd-analyze.html


The Core Rule

In systemd, requirement and ordering are different dimensions.

That single fact explains a lot of weird behavior.

Requirement dependencies

These express "what else must exist/be pulled in."

Examples: - Requires= - Wants= - BindsTo= - PartOf= - Conflicts=

Ordering dependencies

These express "what must happen before/after what."

Examples: - Before= - After=

You often need both.


Why This Matters

Suppose unit A depends on unit B.

If you write only:

Requires=B.service

Then systemd will try to start both, but not necessarily in the order you mentally expect.

If you also need startup order, write:

Requires=B.service
After=B.service

That is one of the most common systemd mistakes.


Unit Load Paths

The manager reads unit files from standard locations, roughly:

  • vendor units
  • runtime-generated units
  • administrator overrides
  • transient/runtime state

Admin overrides usually beat vendor defaults.

This is why systemctl edit is so important: you can layer behavior without mutilating packaged files.


Unit Identity

A unit has: - a name - a type suffix - potential aliases - maybe instances if it is a template

Examples:

sshd.service
systemd-journald.service
dev-sda.device
tmp.mount
user@1000.service
getty@tty1.service

Template units

A template uses @.

Example:

getty@.service

An instance:

getty@tty1.service

Templates are how systemd avoids duplicating similar units.


Requirement Dependencies in Plain English

Wants=

Soft pull-in. Try to start the other unit, but failure is not necessarily fatal to this one.

Use this more than you think.

Requires=

Harder dependency. If the required unit fails to start, the dependent unit is considered failed too.

Requisite=

Like "must already be there now." Used less often.

BindsTo=

Stronger lifecycle coupling. If the bound unit disappears, this one goes down too.

Often used with device-ish or mount-ish relationships.

PartOf=

Propagates stop/restart operations. Useful when you want units to move together administratively without fully changing startup requirements.

Conflicts=

Mutual exclusion. Used when states cannot coexist.


Ordering Dependencies

After=

This unit starts after the listed unit is started. It says nothing about whether the other unit should be pulled in.

Before=

Inverse ordering relation.

Again: ordering alone does not pull units in.


Targets Are Not "Runlevels", Except When They Sort Of Are

Targets are synchronization points.

They are often used to represent system state milestones:

  • sysinit.target
  • basic.target
  • multi-user.target
  • graphical.target

You can think of them as graph waypoints.

A target often uses Wants=/Requires= to pull in units and usually implies ordering through target semantics.

But treating them as simple numbered runlevels leaves performance and correctness on the floor.


Default Dependencies

Many unit types automatically receive default dependencies unless disabled with:

DefaultDependencies=no

This is powerful and dangerous.

Default dependencies save you from a lot of boilerplate. Disabling them is mostly for: - early boot units - shutdown-path units - special low-level cases

If you disable defaults without knowing why, enjoy the fire.


Implicit Dependencies

Some dependencies are inferred from unit content.

Examples: - mount units from path relationships - service units using sockets or paths - resource-control relationships - target helper dependencies

This is why the graph you run is larger than the file you wrote.


Conditions and Assertions

Conditions let a unit be skipped based on runtime state.

Examples: - architecture checks - file existence - path non-emptiness - kernel command line - virtualization/container detection

Assertions are stronger: failing them is treated more severely.

This matters because "didn't run" and "failed" are not the same operational event.


Isolation

systemctl isolate X.target asks the system to move toward a target and stop units not part of that target's world.

This is powerful. It can also drop you into a minimal state if you isolate the wrong thing.

Think of isolation as: "Make the system look like target X is the only desired destination."


Instance Relationships and Drop-Ins

Use drop-ins for local customization:

systemctl edit sshd.service

That creates override snippets rather than patching vendor unit files.

This is the sane way to add: - environment variables - restart policy - timeouts - dependencies - resource controls


Graph Debugging

These commands matter:

systemctl list-dependencies multi-user.target
systemctl show nginx.service
systemctl cat nginx.service
systemd-analyze critical-chain
systemd-analyze dot

When debugging: 1. inspect unit file 2. inspect drop-ins 3. inspect effective properties with show 4. inspect journal 5. inspect graph


Typical Broken Patterns

After= without Wants=/Requires=

You told systemd about order, not necessity.

Requires= without After=

You told systemd about necessity, not order.

Using network.target as if it means "network ready"

It mostly means networking stack management has started, not that remote connectivity is truly available.

Using network-online.target everywhere

You traded correctness uncertainty for boot slowness and maybe still got it wrong.

Overusing hard dependencies

This makes systems fragile. Prefer soft pull-ins unless you truly need hard failure propagation.


Decision Table

Need Usually use
Start B when A starts, but A may still survive if B fails Wants= + maybe After=
A must not run unless B starts successfully Requires= + After=
A must stop if B disappears BindsTo= + After=
Admin stop/restart A should also affect B PartOf=
Two states must not coexist Conflicts=

Interview-Level Example

A web service needs local mounts and a database socket, but boot should not fail if optional metrics agent is absent.

A decent answer:

  • local mounts via appropriate mount dependencies
  • database relation with explicit requirement/ordering depending on architecture
  • metrics agent via Wants=, not Requires=
  • avoid blind network-online.target unless actually justified
  • use systemd-analyze critical-chain if boot gets slow

That answer shows graph thinking instead of script thinking.


Fast Mental Model

Requirement answers "what else matters?"
Ordering answers "in what sequence?"
You often need both.

Wiki Navigation

Prerequisites