Units can look correct, systemctl start can report success, and ten seconds later the service sits inactive. Or it restarts every half minute until the start limit stops it. Neither behaviour is a network fault: the unit describes a process lifetime that does not match what the client actually does. The client may fork and exit, or configure the session and exit immediately.

Why the unit goes inactive seconds after a successful start

systemd infers state from the process it started, so Type= is a claim about that process rather than documentation. With Type=simple, the unit counts as active while the command runs and becomes inactive when it returns. A client that hands the connection to a helper and exits therefore looks like a crash, even when the tunnel is up. The journal then shows a start-stop pattern that reads like a failing binary.

Read the unit type next to the client's behaviour before touching the network configuration.

Pick the type that matches the client

Two patterns cover most vendor CLIs: a command that configures a session and exits, and a command that forks a helper before returning. Each needs a different unit shape.

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/bin/protonvpn-cli connect -f
ExecStop=/usr/bin/protonvpn-cli disconnect

oneshot with RemainAfterExit=true keeps the unit active after the command returns, which matches the configure-and-exit pattern, and the stop command gives the service a clean shutdown. A forking client keeps the same shape but needs Type=forking so systemd follows the child. Set Restart=on-failure, RestartSec=30 and a finite StartLimitBurst, so that a provider outage cannot turn into a restart loop which buries the error.

Never let a supervised client daemonise

Supervisors outside systemd hit the same wall. A runit run script that launches a daemonised client returns to the supervisor at once, which reads the return as a crash and starts another copy. Every supervisor expects to own the foreground process, the way sshd -D stays in front. Drop the daemon flag for supervised commands and let the supervisor do the backgrounding. Wrapper scripts that return before the tunnel exists cause the same confusion.

A process that detaches itself is invisible to whatever was supposed to watch it.

The session ends and takes the tunnel with it

A desktop session owns the client it launches. End the login and the session bus disappears, taking the tunnel with it, which explains why a graphical client's connection often drops when the last window closes. Servers should use a system unit or a NetworkManager profile that comes up on its own. A user unit makes sense only when the tunnel is meant to end with the login, so bind it to the graphical session target and state that in its description.

The service user is the other half of the same problem. A key owned by root with mode 0600 is unreadable to a unit running as another account, no matter what sudo achieved in your shell.

systemctl cat openvpn-server@work
sudo chown -R openvpn:network /etc/openvpn/server
sudo find /etc/openvpn -type f -perm /077 -ls

Order dependents after the address exists

When a daemon binds to a tunnel address that has not been created yet, it exits with Cannot assign requested address. The repair is explicit ordering rather than a retry loop.

[Unit]
After=network.target [email protected]
Requires=sys-devices-virtual-net-wg0.device

After= sequences the jobs, while Requires= on the device unit ties the dependent to the interface's existence, which is stronger than waiting for the network target alone. Reload the manager after editing and restart the dependent service, then read its journal instead of assuming the order took effect. Send the client's output to the journal with StandardOutput=journal or a log option, because a supervised client writing to a file nobody opens looks identical to one that never started.

Then reboot once and confirm the unit is active after the tunnel device appears. A fix verified only by systemctl start has not tested the boot order it was meant to repair.

Winndannde jowitiiɗe heen