Reboot the server with nobody logged in, then connect from a network you do not control. That single test answers whether recovery is real, because a tunnel that only works after someone runs a command by hand is not recovery, it is a repair. Most checks stop too early: the daemon shows active, the interface shows up, and the page still does not load. The gap is almost always persistence of the pieces around the tunnel, not the tunnel command itself.

Four owners, four different tests

Before the restart, name what is supposed to bring the tunnel back. The test you run and the fix you apply both depend on that answer, and there are four common owners: a systemd unit for the VPN daemon, a wg-quick instance unit for a WireGuard interface, a container runtime, or a network manager profile. If you cannot name the owner, stop and find it, because a reboot test without an expected startup path only tells you that something happened.

Each owner keeps its configuration somewhere different. A systemd-managed OpenVPN server reads its config on start, so whatever is in the file at boot is what runs. A wg-quick interface derives its addresses, routes and DNS from the same file, which means an interface brought up by hand from a shell and never enabled as a unit will not return. A container brings back its entrypoint but not host firewall changes unless those live outside the container.

Baseline first, or the result means nothing

Capture the working state while it works: addresses on the tunnel, routes for the client subnet, NAT or forwarding rules, the listener socket, and the forwarding sysctl. ip addr show wg0, ip route, nft list ruleset or iptables-save, ss -lunp, and sysctl net.ipv4.ip_forward are enough on most hosts.

Record how the service is configured to start, not just that it is running. systemctl is-enabled wg-quick@wg0 and systemctl is-active wg-quick@wg0 answer different questions, and the enabled state is the one that matters at boot, as described in systemctl(1). For OpenVPN, check which template the host uses, since openvpn@server and openvpn-server@server are different units with different defaults.

Run the restart and read the first failure

Reboot on purpose, during a window you chose, with a console or out-of-band path open. Watch the order of events rather than the final state: host reachable, unit activated, interface present, routes installed, firewall rules present, listener bound, client accepted. The first step that does not happen is the finding; everything after it is noise.

A daemon can be active while the tunnel is unusable, and that is the most common false pass. Startup can complete before the network is online if the unit only waits for network.target instead of network-online.target. Referenced files such as certificates, keys or a CRL can be unreadable at that moment because a mount or a secrets path is not ready yet. Neither problem shows in systemctl status until you read the journal for the unit.

Test from outside the host. A client on the same LAN can succeed through a route that no remote client would ever use, and an SSH session to the box proves nothing about the tunnel. Connect, resolve an internal name, reach the private resource, then reconnect once to confirm revocation state and keys still apply.

The three conditions that decide your fix

If the tunnel returns but clients cannot reach anything, the failure is in routing or NAT persistence. If clients cannot even complete a handshake, the failure is in the interface or the auth material. If the service never starts, the failure is in the unit file or its dependencies. Those three branches lead to different fixes, which is why a blind reboot without a baseline costs more time than it saves.

Make the fix where the setting is owned. Put forwarding in a sysctl drop-in, put NAT rules in the ruleset that loads at boot, enable the unit rather than starting it, and keep secrets on a path that exists before the unit starts. Adding a sleep to the unit hides a dependency instead of declaring it.

Record the result in the runbook

Write down what came back on its own, what you had to start, and how long the gap lasted. Include the exact commands you used to verify, so the next person repeats the test instead of inventing one. If any step needed a manual command, that command belongs in configuration, not in the runbook as a permanent step.

Then schedule the next test for after the next change to the host's network stack, not for a calendar date six months out. The failure mode you are guarding against is a temporary command that felt permanent, and those appear exactly when someone edits firewall rules or moves a service to a new unit.

Nsɛm a ɛfa ho