This episode covers how WireGuard traverses NAT: the endpoint discovery mechanism that learns addresses dynamically, UDP hole punching, and the crucial role of PersistentKeepalive in keeping connections alive behind strict NAT.

Many of the devices that want to connect over WireGuard are behind NAT — a laptop at home, a server in an office, or a node on a mobile network. NAT rewrites source addresses and ports, so the address visible from the outside differs from the device's real address. How does WireGuard still find its way?
The answer lies in UDP's nature and WireGuard's design: endpoints are never considered permanent, and they can always be re-learned. Episode 7 dissects the endpoint discovery mechanism, UDP hole punching, and PersistentKeepalive as the keeper of connections behind NAT.
The key to understanding this episode is accepting that WireGuard never trusts a fixed address. As long as the cryptography is valid, the source address is a fact that can be re-learned at any time — and this is what makes NAT traversal feel almost magical.
WireGuard runs over UDP. Unlike TCP, UDP has no concept of a connection that the stack must maintain. This lets WireGuard handle each packet independently and learn a packet's origin address whenever needed. This property is what makes NAT traversal feel easy compared to TCP-based VPNs.
Additionally, because UDP is stateless, you do not have to manage connection state like in TCP. This simplifies server configuration serving many clients at once, since every packet is counted against the same peer in a uniform way.
The Endpoint field in [Peer] is only an initial hint. When a valid packet arrives from a peer, WireGuard automatically updates its endpoint with that packet's source address. In other words, a peer behind NAT can change IPs and WireGuard will follow as long as that peer sends a packet first.
This behavior also explains why you rarely need to update Endpoint manually when a client switches networks: as soon as the client sends a packet, the server's WireGuard records the new address and starts using it immediately. Episode 19 will take advantage of this property for failover.
sudo wg show wg0In the wg show wg0 output, the endpoint column shows the address currently in use. If that address differs from the Endpoint in the configuration file, it is proof that endpoint discovery is working.
A changing endpoint is also a valuable diagnostic hint. If the endpoint keeps changing while the client switches networks, that is normal; if it never changes even though the client switched, keepalive packets are likely not getting out.
When two devices are both behind NAT, each can only be reached from the outside through the mapping that its NAT created when the device sent a packet out. The strategy: both sides send a packet to a known address first, so each NAT records an outbound mapping, and packets arriving through that mapping get forwarded.
This is called UDP hole punching. WireGuard supports this pattern because of its stateless UDP nature. To succeed, ideally one of the NATs is cone-shaped (not symmetric), and both devices know an address reachable from the outside.
Hole punching works best when a device has already sent packets to the destination, so the outbound mapping already exists. That is why, in a two-behind-NAT scenario, one side usually initiates the connection first and the other side waits for the first incoming packet.
On a symmetric NAT, outbound mappings are differentiated per destination. Two devices behind symmetric NAT are almost impossible to connect without help. For this case, at least one side must be directly reachable — usually a server with a public address or port forwarding. That is why almost every WireGuard deployment places the server on a public address.
The design principle to remember: NAT traversal is not WireGuard's primary goal; it is a side effect of its UDP-based design. This is why WireGuard stays simple even when facing complicated networks.
NAT mappings usually expire after tens of seconds to a few minutes without traffic. If you go quiet, the mapping can disappear and the server can no longer reach the client. The solution: send keepalive packets periodically.
[Peer]
PublicKey = <kunci publik server>
Endpoint = 203.0.113.5:51820
PersistentKeepalive = 25A value of 25 seconds is the standard recommendation for a client behind NAT: frequent enough to keep most NAT mappings alive, and frugal enough to preserve mobile device battery.
sudo wg set wg0 peer <PUBLIK_SERVER> persistent-keepalive 25The wg set wg0 peer ... persistent-keepalive 25 command changes the keepalive without taking the interface down. A value of zero disables the keepalive.
Keepalives do not interfere with normal transfer: keepalive packets are small and counted separately from data traffic. Therefore, using them on almost any side that sits behind NAT is a safe decision.
If a peer is on a public address and is always active receiving inbound traffic, PersistentKeepalive is not mandatory. Still, using it is harmless — it only adds a few small packets per minute. As a rule of thumb: set PersistentKeepalive = 25 on the side that is behind NAT.
One more thing to note: too large a keepalive value lets the NAT mapping expire between two sends, while too small a value just wastes bandwidth. Twenty-five seconds is a proven sweet spot in the field.
The most standard remote access scheme:
ListenPort = 51820 opened in the firewall.Endpoint pointing to the server, PersistentKeepalive = 25.If you use this pattern, also note that the Endpoint address in the client's configuration file may be a DNS-resolved hostname. If the server's public address changes, you only need to update the DNS record, without touching the client configuration.
With this scheme, the client can always be reached by the server after the first keepalive, and the server can always be reached by the client through its fixed endpoint. This combination is what most real-world WireGuard deployments use.
With this pattern, you also get a simple observability win: wg show on the server always shows the active client endpoint, so client network changes are visible immediately without having to ask the user.
Finally, do not forget to test a real network-switching scenario: switch from Wi-Fi to a mobile hotspot and observe how long the tunnel takes to come back. The results prove whether your NAT traversal configuration is correct.
Episode 7 completed the NAT traversal topic: WireGuard learns endpoints dynamically from incoming packets, UDP hole punching lets two sides behind NAT talk to each other, and PersistentKeepalive keeps mappings alive.
Key takeaways:
Endpoint is an initial hint; the real endpoint is learned from incoming packets.PersistentKeepalive = 25 maintains the mapping for clients behind NAT.In episode 8 we cover tools and monitoring — the wg and wg-quick commands in full, reading wg show wg0 latest-handshakes and transfer, and writing bash scripts for tunnel health checks.