Boot the machine, log in, and learn that the tunnel came up and died a few seconds later. The profile was fine, the certificate was fine, and the only trace left is the journal from that boot. What usually happened is a race: the unit started before the machine had an address, and the ordering it declared did not mean what it looks like. Everything below is about proving that race from evidence and closing it properly.

The target is a meeting point, not a manager

network-online.target has no idea what a network is. It is a coordination point that collects whatever service is supposed to delay start-up until the link is usable, and it delegates the definition to whichever network manager owns the machine. On most desktops and servers that means a wait-online service shipped by NetworkManager or by systemd-networkd. Enable neither and the target has nothing to wait for, so it completes the moment something pulls it in, which can be well before DHCP answers. The ordering line in your unit was honest and still useless.

The first question is therefore not what your unit declares. It is whether anything on this machine is actually doing the waiting.

Read the last boot instead of the unit file

Unit files describe intent, and the journal describes what happened. Ask for that boot only, with monotonic timestamps so every message sits on one clock. Then find two lines: the moment the tunnel unit began starting, and the moment the address appeared or the wait-online service finished. Order those two numbers and the race is either confirmed or excluded.

The dependency view corroborates it. If a critical chain for the tunnel unit contains no wait-online service, nothing in that path delayed start-up, and the target did not do the work you assumed. Two views, one conclusion.

systemctl list-units --type=service --state=running | grep wait-online
journalctl -b -o short-monotonic -u openvpn-client@office

Add the wait, then the retry

Use a drop-in rather than editing a packaged unit, so a package update does not silently discard the change. Two directives matter: the unit has to be ordered after the target, and it has to pull the target into the same transaction. Ordering alone does not summon the target; wanting it does.

The packaged OpenVPN client unit already carries both. Hand-written units usually carry one or neither, which is worth checking before you add anything.

[Service]
Restart=on-failure
RestartSec=15

Why a fixed sleep is a trap

A sleep in ExecStartPre feels like an ordering fix and behaves like a delay with extra steps. It slows every boot, including the ones that were healthy, and it still loses when the link takes longer than the number you chose.

Worse, it hides whether the wait-online service works, so the next failure presents as a brand-new problem rather than the same one with different timing. A short restart delay is the better instrument: it costs nothing on a healthy boot and converts a lost race into another attempt. Keep the interval modest and let the unit succeed. If it restarts forever, the profile is broken and no amount of retrying will change that, so read the log instead of raising the interval.

When order is correct and names still fail

An active unit with a tunnel route means ordering did its job and the remaining fault sits one layer up. The usual shape is a resolver setting applied by the tunnel's up script before the link or the resolver was ready to keep it. Compare the resolver state during the failure against the state after a restart of the tunnel unit.

If the restart fixes lookups, you have a start-up ordering defect rather than a wrong DNS server, and those two need different fixes. Then prove the whole thing with a cold boot instead of a service restart, because restarting runs against a network that is already up and will hide exactly the defect you are chasing. Repeat it with the link deliberately slow, and treat a unit that only works when the network is fast as still broken.

Qillqakuna t'inkisqa