The home box opens the tunnel and reaches the cloud server without trouble. The cloud server can reach the home application for a minute or two after the home box last sent something, and then stops. Nothing appears in the WireGuard log, no handshake error is reported, and restarting the tunnel fixes it briefly. That pattern is a mapping that expires, not a broken tunnel, and the runbook below confirms it before changing anything.

Confirm the pattern is idle expiry

The distinguishing detail is timing. If the reverse connection works immediately after the home peer sends traffic and fails after a quiet interval, the path is fine and the state in the middle is not. If it never worked, even right after traffic, this is the wrong diagnosis.

sudo wg show wg0 latest-handshakes
sudo wg show wg0 endpoints
sudo wg show wg0 transfer

Run the first two immediately after the home peer sends something, wait five minutes, and run them again. A handshake timestamp that refreshes on schedule with no new endpoint tells you the tunnel is alive. What changed is the ability of unsolicited packets to arrive from outside, which is a property of the path rather than the tunnel.

Then make a reverse attempt and watch the transfer counters. If the cloud side sends bytes and the home side receives none, the packet left the cloud and died in a stateful device on the way in. That is the signature you are looking for. If both counters move, the tunnel delivered the packet and the application is the suspect.

Put the keepalive on the peer behind the router

Persistent keepalives keep a NAT mapping and any firewall state alive by sending a small authenticated packet on a schedule. The setting belongs on the machine behind the router, in its entry for the remote peer, because that is the side whose outbound packet creates the state.

[Peer]
PublicKey = <cloud peer public key>
AllowedIPs = 10.71.0.1/32
PersistentKeepalive = 25

Merge it into the existing peer section. Adding a second block with the same public key is not a valid way to add options, and wg-quick will not merge them for you. The documented 25 seconds is an example chosen to sit under common UDP timeouts, not a value with any special meaning.

Putting the same directive on the cloud side does not help this problem. That peer's packets are already arriving from a public address, so there is no mapping to preserve in the direction that matters. It adds traffic and changes nothing.

Read the timers instead of guessing an interval

Keepalives work because they arrive more often than the state expires. That makes the timeout the number you actually need, and on Linux it is readable on the device doing the translation:

sysctl net.netfilter.nf_conntrack_udp_timeout
sysctl net.netfilter.nf_conntrack_udp_timeout_stream
sudo conntrack -L -p udp --dport 51820

The first value is the timeout for a UDP flow that has only been seen in one direction, and the second applies once replies have been observed. Both are commonly short, and a mapping that goes quiet will follow whichever applies to your flow.

Compare that number with your interval. A keepalive of 25 seconds under a 30-second timeout works; the same keepalive under a 15-second timeout does not. No amount of retrying changes that arithmetic. Some consumer routers and mobile carriers use much shorter values than the Linux default, which is why a configuration that works on one network fails on another.

ObservationWhat it meansReasonable response
Works after traffic, dies when idleMapping or firewall state expiringLower the keepalive interval
Never works, even immediatelyNo return route, listener or firewallStop tuning keepalive
Works for hours, then dies onceCarrier-side translation or address changeCheck the endpoint and logs together

Check the listener and the firewall separately

A live mapping is necessary and not sufficient. The application on the home box has to be listening on the right address, and the host firewall has to permit the packet after it is decrypted.

sudo ss -lntup | grep 8080
sudo wg show wg0 allowed-ips
curl -sS -m 5 http://10.71.0.2:8080/health

If the socket is bound only to the physical interface address rather than to the tunnel address, packets arriving on the tunnel are dropped before the firewall is consulted. That failure looks identical to a mapping problem from the cloud side, and it survives every keepalive adjustment.

The allowed list check is equally important in the other direction. The cloud peer must permit the home tunnel address as a source if it is going to reply, and the home peer must permit the cloud address as a destination. Keepalive packets are authenticated traffic and they exercise the same rules, which is why a keepalive that appears to work can coexist with an application that does not.

Roll out the change and know its limits

Apply the setting, restart the interface if that is your update method, and then repeat the idle test rather than the immediate one. Wait longer than the timeout you read earlier, make a reverse request, and confirm the counters moved on both ends.

Record the interval and the timeout you measured next to it. A future change to the router firmware, or a move to a mobile connection, can alter the timeout. The note is what lets someone re-derive the value instead of copying it. Two numbers on a line save a whole investigation later.

Persistent keepalives protect state. They do not open a port, they do not add an allowed list entry, and they do not make a listener appear. When the reverse connection never works at all, the fault is one of those three, and the WireGuard quickstart describes the keepalive setting in the same narrow terms.

អត្ថបទពាក់ព័ន្ធ