WGnext
A WireGuard client for iOS and macOS with automatic tunnel failover — switch VPN servers seamlessly when one goes down, without dropping the tunnel.
The Problem
Say you have two home internet connections, each running its own WireGuard server — with different keys, different endpoints, different peers. One connection is fast; the other is a backup. With the official WireGuard app, switching between them is manual: notice your VPN is dead, open the app, deactivate one tunnel, activate the other. If your phone is in your pocket, you just don't have connectivity until you notice.
WGnext is a fork of the official wireguard-apple client (which hasn't seen an update since early 2023) that adds failover groups: an ordered list of tunnel configurations that the client monitors and switches between automatically. When the primary recovers, it fails back. No babysitting required.
Health Detection: Watch the Traffic, Not the Handshake
The first implementation monitored WireGuard's last_handshake_time to detect
dead connections — and it didn't survive contact with reality. Handshake age is only
meaningful when persistent keepalive is enabled and its interval is known; idle tunnels
look stale even when they're fine, and users had to carefully coordinate timeout and
keepalive values.
The shipped engine is traffic-based. Every 10 seconds it polls tx_bytes and rx_bytes from wireguard-go and applies a simple rule: if data is coming back,
the tunnel is healthy; if nothing is being sent, it's just idle; but if the device is sending and receiving nothing, a timer starts. Thirty seconds of
tx-without-rx is real evidence of a broken path — that triggers failover. Idle tunnels never
false-positive, brief glitches get a grace period, and dead connections are caught in about
40 seconds.
Seamless Switching
The interesting discovery: a running tunnel's entire configuration — interface private key, peers, endpoints, allowed IPs — can be hot-swapped in place. The adapter pushes new network settings and a new UAPI config to wireguard-go while the OS-level VPN stays "connected," briefly passing through the reasserting state. No teardown, no VPN permission prompts, no connectivity gap beyond the new handshake itself.
The whole failover engine runs inside the Network Extension process, which iOS keeps alive even when the app is killed. Failover groups are first-class VPN configurations, so on-demand activation rules and the system's single-active-tunnel constraint work natively. Anti-flap guards — a 60-second minimum hold time and a 5-minute cooldown after cycling through every config three times — keep a bad network from turning the client into a ping-pong match.
Background Probes and Hot Spares
Failing back to the primary poses a chicken-and-egg problem: how do you test whether the primary has recovered without moving your traffic onto a possibly-dead connection? The legacy approach live-swaps to the primary, waits up to 15 seconds for a handshake, and reverts if none arrives — a 15-second outage every probe if the primary is still down.
The fix is a second, lightweight WireGuard device running in the same process with real UDP sockets but a null tun device — it can complete a genuine Noise handshake with the primary server without routing any user traffic. Only after the probe proves the primary healthy does the client swap configs. No disruption on failure.
Hot spare mode takes it one step further: the probe for the next failover
target runs continuously, keeping a live session warm. On failover, the null tun is atomically
swapped for the real utun file descriptor inside the running device — the
wireguard-go goroutines never restart and the existing Noise session is preserved, so traffic
flows on the new connection with zero handshake delay. The swap point is a lock-free atomic.Value read costing ~1–2ns per packet.
Interactive Demo
This simulates the failover engine with a group of three tunnels. Cut the active server and watch the health monitor catch the tx-without-rx pattern, time out, and hot-swap to the next config. Restore the primary and the failback probe brings you home. Toggle background probes off to see the legacy 15-second probe disruption, or turn on hot spare for zero-handshake failover. Detection uses the shipped timing constants (10s polls, 30s traffic timeout); the failback probe interval is shortened from 300s to 90s so you're not waiting around.