Affiliate disclosure — This post contains Contabo affiliate links. If you grab a VPS through them, we earn a commission at no extra cost to you. Every value and behaviour below is documented from official WireGuard sources and written to be reproducible on your own machine.
Your WireGuard client connects, works for a minute, then goes quiet: you can reach the server from the client, but the server can no longer push anything back to you. SSH from the server side hangs, a LAN device behind the client becomes unreachable, the handshake silently goes stale. The fix is almost always one line on the client: PersistentKeepalive = 25. This guide explains exactly what that option does, why 25 seconds is the magic number, and which side of the tunnel it belongs on.
The short answer
If a peer sits behind NAT and needs to stay reachable, add this to the [Peer] block on that peer's config:
[Peer]
PublicKey = ...
Endpoint = server.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
Restart the tunnel and the silent-tunnel-after-idle problem disappears. Everything below is the why.
What PersistentKeepalive does
By design, WireGuard is silent: when there is no traffic to send, it sends nothing. No keepalives, no heartbeats, no chatter. That makes it efficient and hard to fingerprint — but it breaks one specific scenario.
When a client is behind NAT (a home router, mobile carrier, office firewall), the NAT device creates a temporary mapping the first time the client sends a packet out. That mapping is what lets the server send replies back to the right internal address — think of it as a punched hole in the NAT. Crucially, that hole expires after a period of inactivity, often anywhere from 30 seconds to a couple of minutes for UDP. Once it closes, the server's packets have nowhere to go, and the peer becomes unreachable until the client speaks first again.
PersistentKeepalive solves this by sending a small, empty keepalive packet to the peer every N seconds. That tiny bit of traffic keeps the NAT mapping refreshed, so the hole never closes and the peer stays reachable even when idle.
Why 25 seconds
The official WireGuard recommendation is 25 seconds, and the reasoning is simple. Common UDP NAT timeouts cluster between 30 seconds and a few minutes, and the shortest value you regularly hit in the wild is around 30 seconds. Sending a keepalive every 25 seconds refreshes the mapping with a small margin before that 30-second floor — frequent enough to never let the hole close, infrequent enough to cost almost nothing.
You can go lower if your NAT is unusually aggressive (15 is a reasonable next step), or higher to save battery on mobile, but 25 is the documented default for a reason. Don't over-think it.
Where to put it: client vs server
This is the part people get wrong. The keepalive goes on the peer that needs to stay reachable through a NAT:
| Setup | Keepalive on client? | Keepalive on server? |
|---|---|---|
| Roadwarrior client behind NAT → server on public IP | Yes (= 25) | No |
| Server on a clean public IP | (n/a) | Not needed |
| Both ends behind NAT (peer-to-peer) | Yes | Yes |
In the normal case — a laptop or phone behind a router talking to a VPS with a real public IP — only the client needs PersistentKeepalive = 25, in the [Peer] block that describes the server. The server doesn't need one toward its clients: the client always initiates, and the server just replies to whatever source address the last packet came from. A clean public-IP server is exactly the setup where you avoid the server-side NAT problem entirely.
Example [Peer] config
Here is a complete client-side peer block with the keepalive in place:
[Interface]
PrivateKey = <client-private-key>
Address = 10.66.66.2/32
DNS = 10.66.66.1
[Peer]
PublicKey = <server-public-key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
Apply it with a tunnel restart:
sudo wg-quick down wg0 && sudo wg-quick up wg0
Confirm it took effect — wg show lists the interval under the peer:
sudo wg show
# persistent keepalive: every 25 seconds
If you are assembling configs from scratch, the ready-to-use WireGuard config templates already include the keepalive line in the right block for each platform.
The "interval must be in range" error
PersistentKeepalive expects a plain whole number of seconds, validated against the range 0 to 65535. If you see interval must be in range or must be in range 0 to 65535, you passed something that isn't a valid integer in that window:
- A unit suffix — write
25, not25s. - A decimal —
25, not25.0. - A negative number, or a value above 65535.
- A stray space or invisible character pasted from a web page.
A value of 0 is legal and means disabled — the same as leaving the line out entirely. So PersistentKeepalive = 0 does nothing useful; use a real interval like 25.
Keepalive set but still not working
If you added the line and the tunnel still goes silent after idle, work through these honest causes in order:
- Set on the wrong side. The peer that's behind NAT is the one that needs it. A keepalive on the public-IP server alone won't keep a client's NAT hole open.
- NAT timeout shorter than your interval. Some carrier-grade NAT closes UDP mappings faster than 30 seconds. Drop the interval to 15 and re-test.
- A firewall is blocking the path. If the underlying UDP port is filtered, no keepalive will get through — confirm the WireGuard port is actually open on both ends.
- The endpoint changed (roaming). If the client moved networks and its public address changed, the server may still be aiming at the old
Endpoint. The keepalive helps the server track the new source, but a stale client-sideEndpointcan still misfire.
Keepalive vs the WireGuard handshake
These are two different timers, and it helps to separate them. WireGuard renews its cryptographic handshake roughly every two minutes — but only when there is actual traffic to protect. On an idle link with no keepalive, no traffic means no handshake renewal, and meanwhile the NAT hole quietly closes.
PersistentKeepalive fills exactly that gap: it keeps a trickle of traffic flowing on an otherwise idle link, holding the NAT mapping open between handshakes so the tunnel is ready the instant real data needs to move. The keepalive doesn't replace the handshake; it keeps the path alive so the handshake can happen when needed.
A public-IP server removes half the problem
Most keepalive headaches come from NAT on the server side — a VPN box behind a home router, double NAT, a provider firewall. Put the server on a clean public IPv4 and the only NAT left in the picture is the client's, where a single PersistentKeepalive = 25 is all you need. A Contabo Cloud VPS 10 at €5.50/month gives you full root and a real public IP with no provider firewall to fight — the exact conditions where the client-side keepalive is the only thing you have to set. To understand the NAT mechanics this option works around, see the plain-English explainer on what NAT is.
Going further
- What port WireGuard uses and how to open it
- What is NAT, explained simply
- Ready-to-use WireGuard config templates 2026
- WireGuard MTU: fix the stalled tunnel
- WireGuard handshake did not complete: the full fix list
Sources and references:
- WireGuard quickstart and wg-quick docs
- WireGuard protocol and design — wireguard.com
- Arch Wiki — WireGuard (PersistentKeepalive notes)
Published 2026-06-29. The 25-second recommendation and the behaviour of PersistentKeepalive are from official WireGuard documentation; NAT timeout ranges vary by device, so confirm against your own router or carrier if a shorter interval is needed.
Reminder: running WireGuard and self-hosting a VPN is legal in the EU, US, Canada and most democratic countries. VPNSmith publishes this content for educational purposes.
★ Nuremberg GDPR datacenter · ✓ Dedicated IPv4 included · 200+ Mbps guaranteed
Self-host your VPN on your own VPS → ContaboFull root access · public IPv4 · pick your region→
