Imagine two separate rules for a school trip. Rule one: "the bus leaves after the teacher arrives". Rule two: "the trip needs the teacher". The first is only about timing: if the teacher is not coming at all, the bus just leaves. The second is about needing someone: it makes sure the teacher is invited, but without the first rule, bus and teacher might set off at the same moment.
In systemd, After= is the timing rule and Requires= or Wants= is the "needs" rule. You almost always want both. And network.target only means "the road exists"; network-online.target means "the road is open", which is what a service that connects somewhere needs, with both After= and Wants=.
Why this matters
Your app needs a database to be running before it starts. At boot, systemd starts dozens of units at the same time to be fast. If you do not tell it "app needs the database, and must wait for it", your app starts first, cannot connect, and dies
on every reboot, never when you test by hand.
What you need to know already: 2.1 ([Unit] section), 2.3 (drop-ins with systemctl edit), network interfaces and IP addresses (1.13).
Two words
A dependency: "if you start me, also start that". A requirement relationship.
Ordering: "if we are both starting, I go after that one". A time relationship.
The single most misunderstood thing in systemd
After= and Requires= are completely independent. One is about time, the other is about existence.
After=foo.service ordering only: if foo is also being started, wait for it.
Says nothing about whether foo succeeded, or is even
wanted. On its own it does not pull foo in at all.
Requires=foo.service hard dependency: pull foo in; if foo is explicitly
stopped or restarted, so am I. If foo FAILS TO START, I
am skipped too - but only if I am also After= it.
Says nothing about ORDER - without After=, both start
simultaneously.
Wants=foo.service soft dependency: try to start foo, carry on regardless.
The most common and the safest.
So Requires= alone gives you a race: your service and its dependency start at the same instant, and yours may well try to connect before the other is listening (ready to accept connections on its port). You almost always want both. For an app that needs the PostgreSQL database server:
BindsTo= - like Requires, but also stops you if the other unit goes away for any reason (including a device disappearing). Very strict.
PartOf= - stopping/restarting the other unit propagates to you, but not the reverse. The usual way to group workers under one control unit.
Conflicts= - starting me stops the other one. That is how rescue.target (a minimal repair mode, 2.18) pushes out multi-user.target.
network.target is a lie you will believe once
After=network.target means "the network software in the kernel has been set up". It does not mean an interface (like enp0s1) is up, an IP address is assigned, or anything is reachable. A service that needs its own IP address, or connects to another machine, will still fail.
network-online.target means "the network is actually usable". Both lines are needed: it is only started if something wants it, so After= alone waits for a target that nobody is starting - which resolves immediately and does nothing.
Seeing the graph
systemctl list-dependencies ssh # what ssh pulls in, as a tree
systemctl list-dependencies --reverse ssh # who pulls in ssh
systemctl show -p After -p Wants -p Requires ssh
list-dependencies prints a tree: each indented line is a unit pulled in by the line above it; a green dot means active, a white one inactive. It follows targets all the way down - start at a service, not at multi-user.target, unless you enjoy scrolling. show -p A -p B prints several properties at once, one Name=value line each.
What you can now do
make a service wait for another one and pull it in
make a service wait for a usable network
draw a unit's dependency tree
Why it helps
Startup races are a whole family of "it works after a restart" bugs: a service that fails at boot because it started before the database, before names could be looked up, or before its network address existed. You will see them on any server that runs a database and an app side by side, and in reviews of units written with only Requires= or only After=network.target.
list-dependencies --reverse answers the question you must ask before maintenance: "what will break if I stop this?". And PartOf= and BindsTo= are how you make a group of workers stop and restart together. The ordering-versus-needing distinction will come back in many tools later; learning it here, where you can see every file, makes it stick.
Because Requires= only says "start that unit too, and stop me if it fails or is stopped". It says nothing about order, so both start at the same time and your service may try to connect before the other one is listening. After= adds the order: wait until that unit has finished starting. The two are separate on purpose, so you combine them as needed.
What is the difference between Wants= and Requires=?
Both start the other unit when yours starts. With Wants=, if the other unit fails to start, yours starts anyway. With Requires=, a failed start of the other unit fails yours, and stopping the other unit later stops yours too. Wants= is the recommended default because it fails gently; use Requires= only when running without the other unit is truly pointless.
Why is network.target not enough?
network.target is reached when the networking software has been started, not when the network interface has an IP address or can reach anything. A service that listens on a specific address or connects to another machine at startup can still fail. network-online.target waits until the network is actually usable. It only waits if some unit asks for it, hence Wants= plus After=.
What do PartOf= and BindsTo= do?
PartOf=other makes stops and restarts of other carry over to this unit, but not the other way round; it groups workers under a parent, so restarting the parent restarts them all. BindsTo= is a stronger Requires=: this unit is stopped whenever the other one stops for any reason, even a disk being unplugged. Combine BindsTo= with After= to get the order right.
How do I find out what depends on a unit before stopping it?
systemctl list-dependencies --reverse unit shows the units that pull it in. systemctl show unit -p RequiredBy,WantedBy,BoundBy lists those relationships directly. If you stop a unit that others require, they are stopped too. Before maintenance, check the reverse dependencies, and consider mask (2.18) if nothing may start it again while you work.
In an interview Junior
What is the difference between After= and Requires= (or Wants=)?
They are independent: one is about time, the other about existence.
After=foo: ordering only. If foo is also starting, wait for it. It does not pull foo in, and does not care whether foo succeeded.
Requires=foo: a hard dependency. Starting me starts foo; stopping or restarting foo stops me too. On its own it says nothing about order, so both start at the same instant - a race.
Wants=foo: a soft dependency. Try to start foo, carry on regardless. The most common and safest.
So you almost always want a pair: After=postgresql.service with Requires= or Wants=. The classic case is the network: After=network.target does not mean an address is assigned. Use After=network-online.targetandWants=network-online.target. Check with systemctl list-dependencies and systemctl show -p After -p Wants.
Also asked: A service fails at boot but works when you start it by hand. What would you suspect? · What is the difference between network.target and network-online.target? · What do BindsTo=, PartOf= and Conflicts= do?