Andrew Mercer

OpenVPN Client Systemd Service

Part of the OpenVPN Comprehensive Guide.
Covers autostarting an OpenVPN client connection on boot and getting its
routes to reliably come up — the original notes had a working service unit
but flagged routes not coming up on reboot as an unsolved TODO.

Use the packaged template unit instead of a hand-rolled one

The original setup used two custom units (edge-cloud-vpn.service for the
tunnel, edge-cloud-routes.service chained after it via
ExecStart/ExecStop scripts) to add routes as a separate step from
bringing up the tunnel. That split is very likely the actual cause of the
boot-time race: two independently-scheduled services means two chances for
systemd to start them in the wrong order relative to networking being fully
up, especially across a reboot when interface bring-up timing varies.

Current OpenVPN packages ship a template unit,
[email protected], that starts the client for any config dropped in
/etc/openvpn/client/<name>.conf:

sudo cp amercer-pc1.ovpn /etc/openvpn/client/amercer-pc1.conf
sudo systemctl enable --now [email protected]

That alone typically fixes the "routes don't get added on reboot" problem
that the original notes hit: routes pushed by the server, or added via
route/route-up in the client config, come up as part of the same
service's startup as the tunnel itself
, rather than depending on a second
service racing against network readiness. There's no separate route
service to keep in sync.

If you still need custom routing logic

Some setups genuinely need something beyond what the server pushes — a
route to a subnet only this particular client should reach, for instance.
Rather than a second systemd unit, hook it into the OpenVPN client
lifecycle directly with --route-up, which runs after the tunnel and its
pushed routes are already up, guaranteeing ordering without relying on
systemd unit ordering at all:

# in amercer-pc1.ovpn / /etc/openvpn/client/amercer-pc1.conf
script-security 2
route-up /etc/openvpn/client/amercer-pc1-route-up.sh
#!/bin/sh
# /etc/openvpn/client/amercer-pc1-route-up.sh
ip route add 192.168.20.0/24 dev "$dev"
sudo chmod +x /etc/openvpn/client/amercer-pc1-route-up.sh

This keeps route management inside OpenVPN's own event model instead of a
second unit, so it can't run before the tunnel exists and can't be
skipped by a systemd ordering hiccup on boot.

Make sure the network really is up first

If routes (or the tunnel itself) still fail to come up on reboot after
switching to the template unit above, the remaining usual cause is the
service starting before the underlying network interface has an address —
network.target (used in the original custom units) only guarantees
network services have started, not that any interface is actually
configured yet. Depend on network-online.target instead, which does wait
for that:

# /etc/systemd/system/[email protected]/override.conf
[Unit]
After=network-online.target
Wants=network-online.target
sudo systemctl daemon-reload
sudo systemctl restart [email protected]

(network-online.target needs a provider service actually enabled to mean
anything — systemd-networkd-wait-online.service or
NetworkManager-wait-online.service, whichever manages the interface. If
neither is enabled, network-online.target is reached immediately and
this doesn't help — check with systemctl status NetworkManager-wait-online.service / the networkd equivalent.)

Verifying on next boot

sudo reboot
# after it comes back up:
systemctl status [email protected]
ip route show

If the tunnel interface and expected routes are both present without
manually restarting anything, the ordering fix held.