← Back to Wiki
Networking / WireGuard

WireGuard Handshakes but No Traffic Flows: wg set Adds Crypto Routing, Not a Route

This is a maddening one. wg show reports a handshake seconds old and transfer counters climbing in both directions, so the tunnel is obviously up. Meanwhile every ping to the peer times out, every TCP connection hangs, and nothing in any log complains. The tunnel really is up. What is missing is a kernel route, and adding a peer the live way never creates one.

Share on X

Two different things both called AllowedIPs

AllowedIPs does two separate jobs, and only one of them happens when you add a peer at runtime:

wg-quick does both when it brings an interface up, reading AllowedIPs from the config file and installing matching routes. wg set does only the first. The peer is fully configured as far as WireGuard is concerned, and the kernel has never been told the address is reachable through the tunnel, so packets go out of your default gateway instead and vanish.

BE WARNED: the handshake proves nothing about whether your traffic can flow. A handshake is the two endpoints agreeing over the public internet, using the Endpoint address, which routes perfectly well over your normal default route. Byte counters climb because keepalives and handshakes are real traffic. Every one of those signals can look healthy while no application packet has ever entered the tunnel.

The one command that shows it

Compare the broken peer against one you know works. This is the whole diagnosis:

ip route get 10.10.20.10    # a working peer
# 10.10.20.10 dev wg0 src 10.10.20.1 ...

ip route get 10.10.20.14    # the new one
# 10.10.20.14 via 192.168.1.1 dev eth0 ...   <-- out the front door

via your LAN gateway on a physical interface is the failure, stated outright. The kernel is sending tunnel traffic to your router, which has no idea what to do with it and drops it silently.

Why a /30 or /32 hub address hides it

On many hub-and-spoke setups the hub's own tunnel address is configured as a /24, which installs a connected route covering the whole tunnel subnet. Every peer inside that range is then reachable whether or not it has its own route, so wg set appears to work fine and this bug never surfaces.

Give the hub a /32 or a /30, which plenty of setups do, and there is no covering route. Now every peer needs its own, and the first one you add live breaks. That is why this hits some setups constantly and others never.

The fix, without dropping your other tunnels

Add the route for the running interface, and put the peer in the config file so the next restart does it for you:

# 1. the running interface, right now
sudo ip route add 10.10.20.14/32 dev wg0

# 2. and persist it, so wg-quick recreates it on the next start
sudo tee -a /etc/wireguard/wg0.conf >/dev/null <<'EOF'

[Peer]
PublicKey = <peer public key>
AllowedIPs = 10.10.20.14/32
EOF

ip route get 10.10.20.14   # must now say dev wg0
Do not "fix" this by restarting the interface. wg-quick down wg0 && wg-quick up wg0 would install the route, and it also drops every other tunnel on that interface while it happens. On a hub with several peers that is an outage to fix a routing entry. Add the route live, write the config for next time.

Verify with real traffic, not with wg show

The whole lesson of this bug is that the tunnel's own status output cannot tell you what you need to know. Test the thing you actually want to work:

ping -c3 10.10.20.14
timeout 5 bash -c "echo > /dev/tcp/10.10.20.14/8007" && echo OPEN || echo BLOCKED

If ping works and the port does not, you have a routing fix that worked and a firewall on the far end, which is a completely different and much more ordinary problem.

When this isn't your problem

The one-line version: wg set configures the peer inside WireGuard and never touches the routing table, so the handshake succeeds and packets leave through your default gateway. Compare ip route get against a working peer, add the /32 route live, and append the peer to the config so the next restart keeps it.