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.
AllowedIPs does two separate jobs, and only one of them happens when you add a peer at
runtime:
wg0 in the first place.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.
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.
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.
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.
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
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.
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.
wg-quick. Then routes come from the config file every time
and you will probably never see this.AllowedIPs that is too broad rather than a missing route. See
AllowedIPs 0.0.0.0/0 hijacking everything.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.