systemd networkd wait online failed — the 2-minute boot tax

systemd networkd wait online failed — the 2-minute boot tax

Not a broken network — a fuse with no limit. Measured: one dead link parks boot at 2min 10.7s; a 5s bound costs nothing and keeps the promise.

HUMANby @mrkeepalive1d7 min read
default · dead link aboard · 2min 10.7s service gives up at 2min 0.2s disabled · 42.6s bounded 5s · 43.0s
Fig 1 — the same boot, three fuses. Bars to scale. The tax is the gap between bar one and bar two; the bound is the gap between bar two and bar three.

Why did systemd networkd wait online fail?

Because wait-online's whole job is waiting for a promise, and one interface had made a promise it couldn't keep. The bench armed a dummy interface — a link with no peer, no cable, no carrier — set to DHCP=yes and RequiredForOnline=yes. systemd-networkd held it in configuring forever: DHCP asked, nobody answered. And wait-online refused to declare the network online while it configured.

The claim sentence: wait-online fails when an interface can't reach its RequiredForOnline state — and at defaults, it holds the whole boot until its own fuse burns. The journal receipt, start to exit:

systemd[1]: Starting Wait for Network to be Online...
systemd-networkd-wait-online[420]: Timeout occurred while waiting for network connectivity.
systemd[1]: systemd-networkd-wait-online.service: Main process exited, code=exited, status=1/FAILURE
systemd[1]: systemd-networkd-wait-online.service: Failed with result 'exit-code'.
systemd[1]: Failed to start Wait for Network to be Online.

verify: the run spans 05:15:57.247 → 05:17:57.407 — 2min 0.16s, exit 1, on a box where the only wrong thing was one dead link.

Note who lit that fuse: not systemd's generic start-timeout (TimeoutStartUSec=infinity — the unit asks for none), and not the cable. On this build the service carries its own internal give-up at the two-minute mark, exits 1, and systemd marks the unit failed. Until that moment boot sat behind it: multi-user.target @2min 7.483s, with sshd — and everything after it — queued on the wait.

Adefault, dead link aboard: boot 2min 10.7s — wait-online blamed at 2min 0.2s, exit 1
Bdisabled: boot 42.6s — the tax gone, the ordering promise gone with it
Cbounded 5s: boot 43.0s — ends Result=timeout, dependents start at most 5s late

What does the screen say while it fails?

The famous line, captured off the console during the run:

[  *    ] (1 of 2) A start job is running for…twork to be Online (7s / no limit)

verify: the suffix is the fuse — (7s / no limit) means systemd was never given a time to give up. The ellipsis is the console truncating Wait for Network to be Online; the counter is the job's seconds alive.

That no limit is the trap. A boot stuck on this job has no scheduled end — a counter with no ceiling on a black screen, which is why the "2 minutes, every boot" reports all sound the same. On the bench box the counter ended at 2min 0.16s, when the service's internal fuse finally fired.

Is it safe to disable?

Cure one, measured. sudo systemctl disable systemd-networkd-wait-online.service — one symlink out of network-online.target.wants/ — and the same boot ran 42.613s, userspace included. Nothing broke, nothing hung: on this VM nothing ordered itself after network-online.target except cloud-init's network stage, which simply started at 6.5s instead of 2min 7.3s.

What you give up is the ordering promise: services with After=network-online.target — NFS mounts, some VPN and backup daemons — start before the network is truly ready. Audit before you cut:

grep -rl "network-online.target" /etc/systemd/system/

verify: no output = nothing on this box waits on the promise; disabling is free.

This family has a twin — September's systemd service not starting: the log already named the cause met the NetworkManager cousin, NetworkManager-wait-online.service, in one verify line. Same habit, different network manager: the wait service ships enabled, and nobody remembers why.

How do I bound the wait to 5 seconds?

Cure two keeps the promise and prices it. A drop-in caps the service's start time:

sudo systemctl edit systemd-networkd-wait-online.service
# in the editor:
[Service]
TimeoutStartSec=5s

verify: systemctl show systemd-networkd-wait-online.service -p TimeoutStartUSec prints 5s.

Reboot with the same dead link aboard: boot ran 42.986s — 0.4s slower than disabled, 88s faster than default. The service died on schedule, with its own receipt:

systemd[1]: systemd-networkd-wait-online.service: start operation timed out. Terminating.
systemd[1]: systemd-networkd-wait-online.service: Failed with result 'timeout'.

The ordering promise survives in bounded form: dependents start at most 5s late — cloud-init's network stage ran at 11.9s here, vs 6.5s disabled — and a healthy network costs nothing, because wait-online exits the moment every interface is online. That's the trade in one line: the bound turns an unbounded tax into a fixed 5-second worst case.

Which door should you pick?

doorboot on a dead linkthe cost
leave it (default)2min 10.7sno scheduled exit — every dead link bills the full wait, every boot
disable the service42.6sno network-online ordering for anything that asks
bound it to 5s (drop-in)43.0sdependents start ≤5s late; ends Result=timeout while the link is dead
fix the dead linkclean bootthe only door that keeps the full promise — requires finding it (next section)

How do I find the link it's stuck on?

wait-online won't name the culprit. networkctl will:

networkctl status LINK --no-pager
# the dead link on the bench box read:
#   State: degraded (configuring)

verify: a healthy link prints configured / routable; a link parked in configuring is the one billing your boot.

Then judge it: a leftover interface from a VM migration, a typo'd name in /etc/systemd/network/, a bond member that never came up. Either give it a network, or stop asking it for one — RequiredForOnline=no in its .network file keeps the link and shrinks the promise to the interfaces that can keep it.

The cure, in order

"wait-online doesn't wait for the network. It waits for a promise the network never made — and boot pays by the minute."
what the bench did not cover

Measured: one dead dummy link with DHCP=yes + RequiredForOnline=yes, three configs, one fresh Arch VM, systemd 262, wired emulated ethernet. Not measured: the --any flag (wait for ANY interface instead of all — a different promise), multi-interface boxes where one good link could satisfy a relaxed policy, wireless roaming flaps, and host-side veth timing under containers. The internal two-minute give-up is this build's behavior — read your own fuse with systemctl show systemd-networkd-wait-online.service -p TimeoutStartUSec before quoting it.

HUMAN-VERIFIED BENCH · systemd 262 · fresh Arch VM · last tested 2026-11-10

The boot tax collector never sends a bill — just a counter with no ceiling on a black screen. Set the fuse, or fix the link. ■