Adding a new WireGuard peer for one narrow purpose — monitoring, a mail relay, whatever — feels like a small, low-risk change. One setting, if you get it wrong, can quietly reroute every other connection on that host into a dead end, including the SSH session you're using to fix it. This happened for real, took down two live public services for about 49 minutes, and the actual cause had nothing to do with the thing the tunnel was supposedly for.
AllowedIPs actually controlsOn a WireGuard peer definition, AllowedIPs looks like it just answers "which destination
addresses does this tunnel carry." It does more than that. On Linux, wg-quick uses that value
to build real kernel-level policy routing (ip rule entries keyed off an fwmark) so that
traffic matching it gets forced through the tunnel's routing table instead of the host's normal one. Set it
to a single peer's address — a /32 — and only traffic to that one address is affected, exactly
as expected. Set it to 0.0.0.0/0, ::/0 (a full-tunnel or "road warrior" config, the kind meant
for a laptop that should route all its traffic through a VPN) on a server that's supposed to keep
doing everything else it was already doing, and you've just told the kernel to route all outbound
traffic from that host through this one new tunnel — its own SSH replies, its own outbound HTTPS,
everything.
A new WireGuard tunnel was being added to a production server for an unrelated reason (setting up monitoring), using what looks like it started from a standard full-tunnel client template rather than one scoped to the single monitoring host it needed to reach. Within minutes, the server's live public services — completely unrelated to whatever the new tunnel was for — went totally unreachable: no ping, no HTTPS, no SSH. About 49 minutes of downtime before the actual cause was found.
None of these were the cause, but each looked plausible enough to chase first — worth knowing about so you don't repeat the detour:
Two commands confirmed it directly:
wg show
The new peer's entry had allowed ips: 0.0.0.0/0, ::/0 and — the real giveaway — no
latest handshake line at all, unlike every other, healthy tunnel on the box, which
all showed one. A peer with no handshake is a peer that has never actually connected.
ip rule show
This showed the fwmark-based policy rule from the new tunnel, ranked ahead of the normal routing tables — confirming it was intercepting traffic before the kernel ever got to the routing table that would have delivered it normally.
wg-quick down <interface> on the bad tunnel — this also tears down the policy-routing
rules it created, restoring normal connectivity immediately.systemctl disable wg-quick@<interface> so it doesn't come back on the next reboot.Verify with real external checks, not just "the service looks up in a log somewhere": an actual
curl/ping from outside the network, and — if anything on the box depends on a
separate tunnel that was never touched — confirm that one's still carrying real traffic too, not just
reporting healthy.
The fix above is a cleanup. The actual prevention is simpler than it looks: scope
AllowedIPs to exactly the one address you need to reach, every single time —
a /32 (or /128 for IPv6) for a single peer, never a broad range, on any tunnel
whose purpose is "reach this one specific thing," which is nearly every server-side WireGuard use case.
A tunnel built this way, done correctly on a similar project, looked like this: a downloaded/generated
client config defaulted to the same dangerous 0.0.0.0/0, ::/0 — hand-edited down to the
single peer address before ever installing it, so only that one destination's traffic could possibly be
affected, no matter what else went wrong with the tunnel.
Worth going further, too: even with AllowedIPs correctly scoped, add a firewall rule on the
receiving end that only permits this one new peer to reach the specific port it actually needs — nothing
else on the internal network. That way, a mistake in the tunnel config and a mistake in the firewall config
would both have to happen at once for anything to go wrong, instead of either one alone being enough.
Address field to something that doesn't collide) but the underlying lesson is the same as
above: a config file generated by someone else's tooling is a starting point, not something to trust
blindly. Open it and read it before installing it, every time — especially the two fields that determine
what it's allowed to touch: Address and AllowedIPs.