Two Ubuntu machines from the same image, one VPN profile, opposite results at login: the tunnel is up before the desktop appears on one and never comes up on the other. The profile is not the variable here. What differs is which of three components was told to start it, and those three do not share a switch.

Comparing them side by side is faster than guessing. Each one fails in a recognisable way, and that is what makes the comparison worth doing.

Three Starters With Three Different Triggers

The packaged openvpn service is the oldest path. Its init script reads an autostart list from /etc/default/openvpn. If that list is commented out or left empty, the script starts every configuration it finds in that directory rather than the single one you added. One file added for one tunnel can quietly bring up several.

StarterWhat triggers itTypical failure
Init script and templated unitsBoot, plus anything in the autostart listStarts every config, not the one you meant
NetworkManager secondaryActivation of the base Wi-Fi or Ethernet profileNothing happens until that base link comes up
Hand-written unit or wrapperWhatever its WantedBy and ExecStart sayRuns a different binary or environment than your shell

The second row is the one people misconfigure. NetworkManager holds the imported profile and will start it, but only as a secondary of another connection, which means the trigger belongs to the network link rather than to the VPN.

Compare the Setup Cost and the Scope

The init-script route costs one line and covers every config on the box. That is fine for a router and wrong for a laptop that joins different networks. The NetworkManager route costs a UUID lookup and binds the tunnel to one base connection. It comes up on the work SSID and stays down on a hotel network.

A hand-written unit costs the most and buys the most control, including ordering against other services. Scope is the deciding factor. If the machine has one network and one tunnel, the packaged service is honest and predictable. If the tunnel should follow a specific Wi-Fi network, the secondary is the only one of the three that expresses that condition without extra scripting.

Pick One Owner and Remove the Others

A profile started by two owners produces timing that changes between boots, which is worse than either mechanism alone. Choose the owner, then disable the competing path on purpose rather than by accident.

# who is configured to start something?
grep -v '^#' /etc/default/openvpn
systemctl list-units 'openvpn*'
nmcli -g connection.secondaries connection show 'Work Wi-Fi'

# NetworkManager: attach the tunnel to the base link by UUID
TUNNEL=$(nmcli -g UUID connection show 'Corp Tunnel')
nmcli connection modify 'Work Wi-Fi' +connection.secondaries "$TUNNEL"

If the packaged path is the one being retired, do it in the right order: set AUTOSTART to none, run systemctl daemon-reload so the generated units pick up the new list, then disable the templated instance you no longer want. Skipping the reload leaves the old units active, which looks like the change had no effect.

A stop that hangs is a separate problem with a bounded answer. TimeoutStopSec caps how long systemd waits before sending SIGKILL. A drop-in is the right place to set it, because editing the packaged unit loses the change at the next upgrade.

Verify What Actually Started

Reboot and read the evidence instead of trusting the configuration file. The unit that owns the tunnel will show an active state and a start timestamp that matches the boot, and the VPN interface will exist before you log in.

systemctl is-enabled openvpn-client@office
systemctl status openvpn-client@office --no-pager
nmcli -f GENERAL.STATE,connection.secondaries connection show 'Work Wi-Fi'
ip -brief link show tun0

One more check belongs here: confirm the unit's ExecStart points at the binary you think it does. A locally compiled client or a snap package often needs an explicit path or a snap run wrapper, because systemd does not inherit your shell's search order. Comparing command -v openvpn with the unit file settles that in seconds.

Write the chosen owner into the machine's notes, next to the profile name. The next person debugging a missing tunnel will start from that line instead of repeating the comparison.

Akụkọ ndị metụtara ya