One WireGuard peer passes traffic and a second one handshakes but never reaches anything behind the tunnel. The working peer tells you the interface, the UDP port and the server key are probably fine. It tells you nothing about the second peer's AllowedIPs, its inner source address or the forwarding rules in between. Work the list in order and stop at the first step whose condition fails.

Step 1: Write down the path the packet should take

Two clients on the same hub do not automatically reach each other. In a hub-and-spoke design the server receives the packet on wg0 and must forward it back out of wg0 to the other peer, which requires IP forwarding and a forward rule. In a direct peer-to-peer design each side needs the other's endpoint and the other's tunnel address inside AllowedIPs. Decide which design you are debugging before you read any configuration.

The expected path also tells you where to look. If the packet should cross the hub, the failed hop is either the sender's AllowedIPs, the hub forwarding, or the receiver's AllowedIPs. If it should go direct, the hub is irrelevant and the endpoint fields on both peers matter.

Step 2: Compare AllowedIPs on both sides, not one

WireGuard uses AllowedIPs as both a routing table and an access list. When sending, the destination address is looked up in the peer's AllowedIPs to choose the tunnel; when receiving, the inner source address must fall inside the sending peer's AllowedIPs or the packet is dropped. A missing range on either side produces the same symptom: transport works, payload disappears.

Read the live state rather than the file, because a reload may have failed silently: sudo wg show wg0 allowed-ips. On the server, the failed peer's entry must include its tunnel address, for example 10.90.0.3/32. On the client, the server peer's entry must include the destination you are testing, whether that is the hub's tunnel address or the remote subnet. Overlapping ranges make the lookup ambiguous and can select a peer you did not intend, so keep them disjoint.

Step 3: Check the inner source address

A client that routes 10.90.0.3 through wg0 but sources packets from its Wi-Fi address will be rejected by the receiver, because the source does not match the peer's AllowedIPs. Confirm what the kernel actually selects with ip route get 10.90.0.3 from 10.90.0.2, and confirm the address assigned to the tunnel interface with ip -brief addr show wg0.

On the receiving Linux host, reverse-path filtering is a second gate. A strict rp_filter on the interface that owns the route back can drop tunnel traffic whose return path does not match the interface it arrived on. Check the effective value before disabling it; in many topologies the correct fix is a route, not a relaxed filter.

Step 4: Review hub forwarding and the return path

If the hub must forward between peers, the kernel needs net.ipv4.ip_forward=1, and the firewall must accept the forwarded flow: in on wg0, out on wg0, from the sender's tunnel address to the receiver's. A common failure is an input rule that accepts UDP 51820 but no forward rule, so the handshake succeeds and the payload dies. List the effective rules with sudo nft list ruleset or the equivalent for your firewall manager.

Then check the reply. The destination host must have a route back to the sender, either through the hub or through its own gateway. If the hub also NATs the tunnel subnet, confirm the translation applies to the forwarded flow and that the reply reaches the hub before the state expires. A one-way failure that looks like a key problem is often just this return route.

Finally, separate handshake from data. A peer that completes a handshake but stalls on large transfers usually has an MTU or MSS problem on that specific path, not an AllowedIPs problem. Test with a small ping first, then a full-size one, and compare the two peers.

Step 5: Change one thing and keep the working peer intact

Make a single change to the failed path and retest both peers. Replacing every peer's AllowedIPs with 0.0.0.0/0 is the fastest way to break the peer that already worked, because it changes the routing decision for all traffic on that interface. Edit the one peer, reload, and read the live state back.

Record which path succeeded, which failed, and the exact command output for each. That comparison is worth more than a general report that the VPN is broken, and it is the first thing the next person will ask for.

Amanqephe ahlobene