Learn WireGuard - NAT Traversal & Endpoint Discovery
Episode 7 of 23

Learn WireGuard - NAT Traversal & Endpoint Discovery

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.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

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.

NAT Traversal in WireGuard

Why UDP Is the Key

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.

Learning Endpoints Dynamically

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.

Check the latest learned endpoint
sudo wg show wg0

In 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.

UDP Hole Punching

Making a Hole in NAT

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.

Limitations of Strict NAT

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.

PersistentKeepalive

Keeping the Mapping Alive

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.

Keepalive for a client behind NAT
[Peer]
PublicKey = <kunci publik server>
Endpoint = 203.0.113.5:51820
PersistentKeepalive = 25

A 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.

Set keepalive live
sudo wg set wg0 peer <PUBLIK_SERVER> persistent-keepalive 25

The 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.

When Keepalive Is Not Needed

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.

Common Connection Scheme

Client Behind NAT, Server on a Public Address

The most standard remote access scheme:

  • Server: public address, ListenPort = 51820 opened in the firewall.
  • Client: behind NAT, 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.

Closing

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.
  • WireGuard runs over UDP, making it stateless and NAT-friendly.
  • UDP hole punching succeeds when at least one NAT is not symmetric.
  • PersistentKeepalive = 25 maintains the mapping for clients behind NAT.
  • Standard scheme: server on a public address, client using keepalive.

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.