Two things get reported as one failure: the laptop moved to a different Wi-Fi network, or a cable went in while Wi-Fi stayed up. The client still says connected, the icon looks healthy, and the tunnel carries nothing. Underneath, only three pieces of state can move - the link, the local address, the default route - and which one moved decides whether the tunnel recovers on its own or needs a reconnect. I check them in that order, because changing account settings has never fixed any of them.

Link, address and route move independently

Roaming between access points on the same network is usually an L2 event. The radio reassociates, the interface keeps its address and gateway, and nothing the tunnel depends on changes, which is why walking across a large office sometimes goes unnoticed. Joining a different SSID is not that: the old interface may go down, DHCP hands out a new address, a new gateway and new resolvers, and the outer flow the tunnel was using stops existing.

Ethernet adds a third shape. Wi-Fi never drops, so no link event fires at all, yet the wired interface appears with its own address and often wins the default route on metric. The tunnel keeps running over a path that is no longer preferred, or the client keeps its socket pinned to the wireless network it started on. Both look the same from the browser: pages stall the moment the cable clicks.

So the first question is not whether Wi-Fi changed. It is whether the address you are bound to, or the route to the endpoint, changed while the tunnel was up.

The outer socket is pinned to an address that just disappeared

Every tunnel is an outer flow: a local address and port talking to the server's address and port. On Linux that is a UDP socket bound to the old source; when the interface holding the address is removed, sends either fail with EADDRNOTAVAIL or leave through the new interface with a source the server has never seen.

The far side keeps its own pin. An IKEv2 SA without MOBIKE is identified by the address pair, an OpenVPN session is keyed to the peer address it learned, and a NAT mapping belongs to the private address, port and public address that created it. Move behind a different NAT and every reply is sent to a mapping that no longer exists. Nothing on your machine is misconfigured; the five-tuple points at a network you left.

Read the state before theorising:

  • ss -unp shows the local address and port each tunnel socket actually holds.
  • ip route get 203.0.113.10 prints the source address and device the kernel would use for the endpoint right now.
  • wg show wg0 endpoints, swanctl --list-sas or the client log shows the peer address the far side believes it is talking to.

If the socket address and the route lookup disagree, you have found the break, and no credential change will touch it.

What each protocol does with a new source address

Clients absorb the change to different degrees, and the table is what I compare against the client config instead of guessing from the reconnect behaviour.

TunnelRecovery behaviourFailure signature
WireGuardUpdates the peer endpoint when an authenticated packet arrives from a new address; no handshake needed if traffic continues.Handshake current, no return traffic; wg show still lists the old endpoint.
IKEv2/IPsecMOBIKE (RFC 4555) moves the SA to the new address; without it the responder discards packets from an unknown source.DPD or keepalive timeouts, then a full rekey or a dead SA.
OpenVPN over UDP--float accepts a packet from a new source mid-session; otherwise the session stays tied to the original peer address.Idle until ping-restart fires, then a visible reconnect.
OpenVPN over TCPThe tunnel is a byte stream, so a changed source address breaks it outright.Immediate reset and a new connection attempt.

WireGuard's roaming rule only works in one direction: the client has to send from the new address first. A socket still bound to the old interface never does, which is why a tunnel can look up while its handshake timer keeps ageing. The protocol description states the endpoint update condition precisely, and it is worth reading before you change a config.

MOBIKE is negotiated, not assumed. If the server does not offer it, the client must re-establish, and that is a fact you confirm from the SA list or the log rather than infer from a spinner.

Route preference decides the path, not the Wi-Fi icon

With both interfaces up, the kernel chooses by metric and prefix length. ip route show default lists the candidates and ip route get 203.0.113.10 resolves the real decision, including the source address. A wired default at metric 100 and a wireless one at 600 means the endpoint moves the moment the cable seats, while every application that opened a socket earlier keeps its old source.

Windows does the same with automatic interface metrics, biased by link speed. Get-NetRoute -DestinationPrefix 0.0.0.0/0 sorts the candidates and Find-NetRoute -RemoteIPAddress 203.0.113.10 returns the interface and source the stack would pick. A client that binds its outer socket to one interface keeps using it after the metric flips; that is a binding decision, not a routing fault.

Android makes the split visible: ConnectivityManager reports available networks and a separate default, and a VPN app that binds a socket to a Network object holds that binding until it re-binds. On macOS, scutil --nwi prints the primary interface and the order the system would use.

DNS, MTU and IPv6 move with the attachment

A new attachment brings new resolvers. With systemd-resolved, DNS is per-link, resolvectl status shows which link answers a name, and a guest network that hands out its own resolver can leave the tunnel's pushed DNS unused until the link is re-preferred. A captive portal makes it worse: the first HTTPS request is answered by the portal rather than the endpoint, and the client records a dead server.

Path MTU changes too. Ethernet and Wi-Fi usually carry 1500 bytes, PPPoE 1492, and mobile links often less. A tunnel that inherits a smaller MTU stalls on large packets while small requests succeed, which reads as a half-broken connection. ping -M do -s 1472 203.0.113.10 sends a 1500-byte packet with DF set; silence means the path is smaller than you assumed.

IPv6 is the other half. A network that hands out a new prefix changes the source a dual-stack client would use, and a v4-only tunnel leaves v6 traffic outside it until policy catches up. ip -6 route show default and ip -6 addr show answer whether the new link has a v6 path at all.

Reproduce the transition instead of describing it

Pick one harmless request and run the same three commands either side of the change: ip -br addr, ip route show default, ip route get 203.0.113.10. Then watch whether the tunnel still emits packets - tcpdump -ni any udp port 51820 for WireGuard, the matching port for anything else. Packets leaving from the old source mean a binding problem. Packets leaving from the new source with no reply mean the far side or the NAT in between.

Note which event coincided with the first failure: link down, address change, or default-route change. Those point at different owners - a client that must re-bind, an address that must be re-acquired, or a route that belongs to a configuration file and not to a permanent bypass rule.

Before calling it fixed, confirm the direct-traffic block still holds during the gap and that names resolve through the new attachment. If the client holds the old endpoint after the switch, fix the roaming or float setting first. If it roams correctly and still gets no reply, look at the server's peer identity and the NAT on the path, and keep both network contexts in the write-up so the next person can compare them.

Tënë sô zo