skip to content

A systemd service unit declares Requires=postgresql.service but has no After= line. Why can it still start before PostgreSQL is up, and what does After= actually change?

level: middleimportance: must knowfreq 72%

answer

  1. two axes, deliberately independent
  2. parallel dispatch is the default
  3. requirement is not a wait
  4. ordering only bites inside the transaction
  5. the pair of lines is the idiom

basics

~20 s

Requirement and ordering are separate axes in systemd. Requires= only says the other unit must be pulled in and must not fail; it never says "wait for it". Without After=, both units are started in parallel, so either can win the race.

solid answer

~50 s

systemd starts everything it can in parallel by design, and dependency strength is a completely different axis from dependency order. `Requires=postgresql.service` tells systemd to queue a start job for PostgreSQL in the same transaction and to fail or stop with it — it does not delay anything, so both jobs are dispatched at once. `After=postgresql.service` is the ordering directive: it holds your start job until PostgreSQL's job has finished and the unit is considered started. The two are also different in scope — `After=` only has an effect when the other unit is actually part of the same transaction, so on its own it will never pull PostgreSQL in. That is why the correct idiom is both lines together: `Requires=` (or `Wants=`) for the requirement and `After=` for the order. It is also why a missing `After=` weakens `Requires=` itself: the rule that a failed requirement blocks your start only applies when the ordering exists.

code

ini · 10 lines
ini
[Unit]
Description=Orders API
Requires=postgresql.service
After=postgresql.service

[Service]
ExecStart=/usr/local/bin/orders-api

[Install]
WantedBy=multi-user.target

go deeper

for a junior

Remember the pairing: a dependency line and an After= line go together. If you only recall one thing, recall that Requires= does not mean "wait for".

for a middle

Explain why the axes are separate — parallel dispatch — and state the exact consequence: both jobs go out at once, and the failed-requirement rule needs the ordering to be meaningful. Mention that ordering is reversed at shutdown.

for a senior

Frame it as an intermittent-bug pattern you have debugged: works on fast hosts, fails on slow ones. Show how you confirm it with list-dependencies --after and systemd-analyze verify rather than by adding a sleep.

for a principal

Argue about where ordering belongs at all: unit ordering only covers processes on this host, so any cross-host dependency has to be handled by the application. Ordering edges are also boot-time coupling that costs parallelism and should be added deliberately.

## Why the axes are separate at all systemd's design goal was to stop booting like a shell script. SysV-style init ran units in a serialised, numbered order; systemd builds a dependency graph and dispatches every job whose prerequisites are satisfied, all at once. To do that it needs to know two different things about a pair of units, and it refuses to guess one from the other: - **Requirement** — must the other unit be here, and what do I do if it is not? (`Wants=`, `Requires=`, `BindsTo=`, `Requisite=`, `PartOf=`, `Conflicts=`) - **Ordering** — is it allowed to run at the same time as me? (`After=`, `Before=`, and nothing else) If `Requires=` implied `After=`, every requirement would serialise the boot, and the parallelism that makes systemd fast would evaporate. So the split is a deliberate feature, not an oversight, and the price is that you have to write both lines. ## What actually happens with only Requires= ```ini [Unit] Description=Orders API Requires=postgresql.service [Service] ExecStart=/usr/local/bin/orders-api ``` systemd builds a transaction containing a start job for `orders-api.service` and a start job for `postgresql.service`. Nothing orders them, so both are dispatched immediately. In practice the small Go binary wins nine times out of ten and dies on connection refused; on a fast machine, or after a page cache warm-up, PostgreSQL sometimes wins and the bug "disappears". That intermittency is the signature of a missing ordering directive. There is a second, subtler consequence. The documented rule is that a failing required unit prevents your unit from starting **only when an `After=` ordering on it is also set**. Without the ordering there is no point in the timeline at which systemd can apply the rule — your job may already have run. So omitting `After=` does not merely lose the wait; it partially disarms `Requires=` itself. ## What After= does, precisely ```ini [Unit] Requires=postgresql.service After=postgresql.service ``` Now your start job is held until PostgreSQL's start job has completed. Three properties are worth stating exactly: 1. **It is pure ordering.** `After=` on its own never causes the other unit to be started. If `postgresql.service` is not part of the transaction — not enabled, not pulled in by anything — the ordering is simply ignored and your unit starts normally. 2. **`Before=` is the mirror image.** `A: After=B` and `B: Before=A` express exactly the same edge. Use `Before=` when you own the earlier unit and cannot edit the later one, typically via a drop-in. 3. **It is reversed at shutdown.** If A is `After=B` at start, then at stop A is stopped *before* B. You get correct teardown ordering for free, which matters when your service needs the database still alive to flush. ## The implicit ordering you already have With `DefaultDependencies=yes`, an ordinary service already carries `After=sysinit.target basic.target` and `Before=shutdown.target`. That is why you never write ordering against filesystem mounts or the early-boot machinery — it is already there. Units that must run earlier than `basic.target` have to set `DefaultDependencies=no` and then state every ordering edge by hand, which is where early-boot units get delicate. ## Reading the effective order on a live host ```bash systemctl list-dependencies --after orders-api.service systemctl list-dependencies --before orders-api.service systemctl show -p After -p Before -p Requires orders-api.service systemd-analyze verify /etc/systemd/system/orders-api.service ``` The `--after`/`--before` forms of `list-dependencies` show the ordering graph rather than the requirement graph, which is exactly the distinction under discussion. `systemd-analyze verify` loads the unit and reports syntax problems and references to units that do not exist — a cheap way to catch a typo like `After=postgres.service` on a host where the unit is actually called `postgresql.service`. A misspelt name in `After=` fails silently, because ordering against a unit not in the transaction is a legal no-op. ## Ordering targets rather than units When the thing you depend on is a class of service rather than a named one, order against a target: `After=network.target`, `After=nss-lookup.target`, `After=time-sync.target`. These are synchronisation points that other units declare themselves `Before=`, so you get a stable edge without hard-coding an implementation. The trade-off is that a target only means whatever its members agree it means, which is why some of them promise much less than their names suggest. ## The rule to leave the interview with Write the requirement and the ordering as a pair, on adjacent lines, every time. `Wants=` + `After=` for an optional dependency, `Requires=` + `After=` for a hard one. A requirement without an ordering is the classic systemd bug: it works on your laptop, fails on the slow VM, and fails differently after every reboot.

  • Your unit works on a fast host and races on a slow VM. How would you confirm the ordering edge is really missing?
    Run `systemctl show -p After -p Requires` on the unit — that prints the resolved properties including anything added by drop-ins or `.wants/` symlinks, which the file alone will not tell you. `systemctl list-dependencies --after` renders the same graph as a tree, and `systemd-analyze verify` catches the common cause: an `After=` naming a unit that does not exist, which systemd ignores silently.
  • You cannot edit a vendor unit file. How do you add an ordering edge to it?
    Use a drop-in: create `/etc/systemd/system/<unit>.d/order.conf` containing just `[Unit]` and the `After=` line, then `systemctl daemon-reload`. The drop-in is merged over the vendor file, survives package upgrades, and `systemctl cat <unit>` shows the merged result. If the edit belongs on the other side, express the same edge as `Before=` in a unit you do own.

saying these in an interview costs you the question

  • Assumes Requires= implies After=
  • Thinks After= will pull the other unit in
  • Adds sleep to ExecStart to fix the race
  • Believes a missing After= only costs the wait, not the failure rule
  • Says ordering must be restated for shutdown

context