IPsec

NAT Traversal (NAT-T): How IPsec Survives a NAT Device

NAT-T: real NAT translation table showing ESP wrapped in UDP 4500 through PAT
In: IPsec, VPN, CCIE, Labs

Every IPsec engineer eventually hits the same wall. The tunnel worked perfectly in the lab, you shipped the branch router, and now the site is behind a broadband modem doing PAT. Phase 1 comes up. Phase 2 does not. Nothing crosses. This is the single most recognisable failure in the whole of IPsec VPN, and the reason for it is embarrassingly simple: ESP has no port numbers, and PAT is a machine that rewrites port numbers.

NAT Traversal (NAT-T) is the fix. This article shows you exactly why raw IPsec dies behind a NAT, what NAT-T actually changes on the wire, and what the failure and the fix look like in real CLI output from a live CML lab, including the capture almost nobody publishes: the NAT device's own translation table.

Why ESP and PAT Cannot Coexist

Start with the protocol numbers, because the whole story is in them.

IKE (Phase 1 and 2) UDP port 500. It is UDP. It has ports. PAT loves it.
ESP (the data) IP protocol 50. Not TCP, not UDP. No source port, no destination port. Nothing to rewrite.
AH IP protocol 51. Authenticates the outer IP header, so any NAT rewrite breaks the ICV. Fundamentally NAT-incompatible.
ESP over NAT-T UDP port 4500. ESP wrapped in a UDP header, so PAT has ports to work with again.

PAT (what Cisco calls NAT overload) multiplexes many inside hosts onto one public address by giving each flow a unique source port. That is its entire mechanism. Hand it an ESP packet and there is no port field to allocate, no port field to record, and no port field to reverse on the way back. Some NAT implementations bolt on an ESP passthrough hack that tracks the SPI instead, and it works for exactly one inside host, until a second one shows up. Cisco IOS PAT does not pretend. It simply cannot track the flow.

AH is worth a sentence of its own because people ask. AH authenticates the immutable fields of the outer IP header, and the source address is one of them. NAT rewrites that address by definition. The receiver recomputes the ICV, gets a different answer, and drops the packet. There is no NAT-T for AH and there never will be. NAT traversal is an ESP-only story, which is one more reason nobody deploys AH.

The Lab

Two site-to-site IPsec endpoints, EDGE1 at the head end on 203.0.113.1 and EDGE2 at the branch. For this test EDGE2 was moved behind NAT1, an IOS router doing plain PAT on its outside interface:

ip nat inside source list PLZ-PAT interface Ethernet0/0 overload

EDGE2's real address is now a private 10.30.99.2. To the rest of the world, including EDGE1, it appears as 198.51.100.6. EDGE2 itself has no idea any of this is happening, which turns out to matter a great deal.

The Failure: Phase 1 Up, Phase 2 Dead

Disable NAT-T on the branch router (IOS enables it by default, so you have to go out of your way to break it):

EDGE2(config)#no crypto ipsec nat-transparency udp-encapsulation

Now ping across the tunnel:

EDGE2#ping 192.168.99.100 source Loopback10 repeat 5
.....
Success rate is 0 percent (0/5)

Total failure. Now look at Phase 1:

EDGE2#show crypto isakmp sa
dst             src             state          conn-id status
203.0.113.1     10.30.99.2      QM_IDLE           1008 ACTIVE

QM_IDLE. That is the healthy, settled state of an IKEv1 Phase 1 SA. The tunnel's control plane is completely up. Authentication succeeded, DH completed, the ISAKMP SA is ACTIVE. If you stopped troubleshooting here you would swear the VPN was fine.

Now look at Phase 2:

EDGE2#show crypto ipsec sa | include #pkts
    #pkts encaps: 0, #pkts encrypt: 0, #pkts digest: 0
    #pkts decaps: 0, #pkts decrypt: 0, #pkts verify: 0

Zero in every column. Not a single packet has been encapsulated or decapsulated. The data plane is stone dead.

Why the split happens exactly here

This is the part worth internalising, because once you see it you will diagnose this failure from across the room.

IKE negotiation rides on UDP port 500. UDP has ports. NAT1's PAT box takes EDGE2's 10.30.99.2:500 packets, allocates a translation, rewrites the source to 198.51.100.6 with some high port, and forwards them. Replies come back, PAT reverses the translation, EDGE2 receives them. Phase 1 completes normally. Phase 2 (Quick Mode) also runs inside IKE on UDP 500, so the IPsec SAs get negotiated too.

Then the router tries to send actual user data, and that data is ESP. IP protocol 50. It arrives at NAT1 with no ports at all. PAT has nothing to multiplex on, no way to build a translation entry, and no way to demultiplex the return traffic. The packets die at the NAT device.

So you get a control plane that works and a data plane that does not. Phase 1 up, Phase 2 at zero, with a NAT anywhere in the path, is the NAT-T signature. Learn those two show commands together and you will never spend an afternoon on this again.

The Fix: Wrap ESP in UDP 4500

Re-enable NAT transparency (or just leave the default alone) and the tunnel comes straight up. Here is what changed.

EDGE2#show crypto session detail
Session status: UP-ACTIVE
Peer: 203.0.113.1 port 4500 fvrf: (none) ivrf: (none)
          Capabilities:N connid:1011 lifetime:00:57:40
  IPSEC FLOW: permit ip 10.30.10.0/255.255.255.0 192.168.99.0/255.255.255.0
        Inbound:  #pkts dec'ed 9 drop 0 life (KB/Sec) 4607998/3597
        Outbound: #pkts enc'ed 9 drop 0 life (KB/Sec) 4607999/3597

Two things to read here. The peer is on port 4500, not 500. And Capabilities:N, where N means NAT-traversal was detected.

Notice what you did not do: you never configured port 4500 anywhere. IOS discovered the NAT by itself. During Main Mode, both peers exchange NAT-D (NAT Discovery) payloads containing hashes of the source and destination IP and port as each side believes them to be. If the hash a peer receives does not match what it computes from the packet it actually got, an address was rewritten in transit, so there is a NAT in the path. Both ends then agree to float the negotiation from UDP 500 to UDP 4500 and to UDP-encapsulate all subsequent ESP. The whole thing is automatic and it happens during Phase 1.

Now the IPsec SA:

EDGE2#show crypto ipsec sa
     local crypto endpt.: 10.30.99.2, remote crypto endpt.: 203.0.113.1
    #pkts encaps: 15, #pkts encrypt: 15, #pkts digest: 15
        transform: esp-256-aes esp-sha256-hmac ,
        in use settings ={Tunnel UDP-Encaps, }

Two lines carry the whole lesson.

local crypto endpt.: 10.30.99.2 is the private address. EDGE2 has no clue it is being translated. It builds and signs its packets from 10.30.99.2 and hands them to the network. The NAT rewrite happens downstream, outside the router's awareness, which is precisely why the outer header must not be authenticated (and precisely why AH cannot play).

in use settings ={Tunnel UDP-Encaps, } is the clincher. In a normal tunnel you see {Tunnel, }. Here you see Tunnel UDP-Encaps: tunnel mode, with the ESP payload encapsulated in UDP. That string is the definitive proof that NAT-T is engaged. If you only remember one grep from this article, make it that one.

Traffic flows: 15 packets encapsulated, 9 decapsulated, pings at 100 percent.

The NAT Device's View

This is the capture that closes the argument. Log into NAT1, the PAT router in the middle, and ask it what it is actually tracking:

NAT1#show ip nat translations
Pro Inside global      Inside local       Outside local      Outside global
udp 198.51.100.6:4790  10.30.99.2:500     203.0.113.1:500    203.0.113.1:500
udp 198.51.100.6:4791  10.30.99.2:4500    203.0.113.1:4500   203.0.113.1:4500

NAT1#show ip nat statistics
Total active translations: 2 (0 static, 2 dynamic; 2 extended)
Hits: 79  Misses: 0

Two entries. Both say udp. One for IKE on 500, one for ESP-in-UDP on 4500. As far as NAT1 is concerned there is no IPsec here at all, just two ordinary UDP conversations between an inside host and an outside host. It allocates ports (4790 and 4791), it builds extended translations, it counts 79 hits and zero misses. This is PAT doing the most boring thing it knows how to do, and that is the entire point.

Compare that with what NAT1 could have done with raw ESP: nothing. No line in the table, no port to allocate, no state to keep. The genius of NAT-T is not that it is clever. It is that it makes IPsec look mundane enough for a PAT box to handle.

The Costs and the Fine Print

8 bytes of overhead, and your MTU

UDP encapsulation adds a UDP header: 8 bytes on every single ESP packet, on top of the ESP overhead you already pay. That is 8 bytes less room for user data before the packet needs fragmenting. If you were already close to the edge (GRE over IPsec over a 1500-byte path is a classic squeeze), NAT-T can be the thing that tips you into fragmentation, and fragmented ESP is a well-known source of intermittent, size-dependent, maddening failures. Fix it at the source: set ip tcp adjust-mss on the LAN-facing interface and lower the tunnel MTU rather than letting the path fragment for you.

NAT keepalives

PAT translations age out. A NAT device holds a UDP entry for a while after the last packet (often 30 to 300 seconds) and then reclaims the port. If your VPN goes quiet for longer than that, the entry vanishes, and the far end's next inbound packet arrives at a NAT device that no longer knows where to send it. The tunnel appears up on both routers and traffic silently dies until the branch happens to send something first. IPsec NAT keepalives exist purely to prevent this: a tiny periodic packet on UDP 4500 whose only job is to keep the translation warm. On IOS that is crypto isakmp nat keepalive 20. Set it. It is cheap, and the failure it prevents is nasty and intermittent.

Firewall rules: 500 AND 4500

The classic operational mistake. Someone opens UDP 500 on the perimeter firewall because "IPsec uses 500", the tunnel negotiates beautifully, and then no data flows because ESP-in-UDP on 4500 is being dropped. It looks exactly like the failure at the top of this article, and the symptom is identical: Phase 1 up, Phase 2 at zero. Permit UDP 500 and UDP 4500 in both directions. If any peer might sit behind a NAT, you need both, always. (And if no NAT is involved, you also need IP protocol 50 permitted for raw ESP.)

Aggressive Mode, IKEv2, and the rest

NAT-D is exchanged in messages 3 and 4 of Main Mode, which is why the float to 4500 happens mid-Phase-1. IKEv2 does the same job with NAT_DETECTION_SOURCE_IP and NAT_DETECTION_DESTINATION_IP notify payloads in the initial exchange, and the resulting behaviour is the same: float to 4500, UDP-encapsulate ESP. The mechanism differs, the concept does not.

Diagnosing It in the Field

show crypto isakmp sa
Look for: QM_IDLE / ACTIVE.
Means: Phase 1 is fine. IKE crossed the NAT on UDP 500. Do not stop here.
show crypto ipsec sa | include #pkts
Look for: encaps/decaps at 0.
Means: Phase 2 negotiated but nothing crosses. With a NAT in path, suspect NAT-T immediately.
show crypto session detail
Look for: port 4500 and Capabilities:N.
Means: NAT detected and NAT-T active. Port 500 plus a NAT in path means it is not.
show crypto ipsec sa (full)
Look for: in use settings ={Tunnel UDP-Encaps, }.
Means: ESP is inside UDP. This is the definitive proof.
show ip nat translations (on the NAT box)
Look for: two udp entries, :500 and :4500.
Means: PAT is tracking both flows. No 4500 entry means ESP never got wrapped.
local crypto endpt.
Look for: a private address (10.30.99.2 here).
Means: the router is behind a NAT and does not know it. Normal, and exactly why NAT-T exists.

One more warning from the same lab. When we moved EDGE2 behind PAT, the head end kept failing Phase 2 with error 64 and peer address 198.51.100.6 not found, because EDGE1 still had a static crypto map entry pointing at EDGE2's old public address. IOS matched the static entry on proxy ID, found the peer did not match, and rejected the proposal instead of falling through to the dynamic map. If you migrate a peer behind a NAT, clean up the stale static entry. There is more on reading those error codes in our guide to troubleshooting IPsec VPN tunnels.

Key Takeaways

  • ESP is IP protocol 50 and has no port numbers. PAT works by rewriting ports. It therefore has nothing to multiplex on and cannot track an ESP flow. That single fact is the whole problem.
  • The signature is Phase 1 up, Phase 2 at zero. show crypto isakmp sa reads QM_IDLE while show crypto ipsec sa shows #pkts encaps: 0, #pkts decaps: 0. IKE on UDP 500 survives PAT; raw ESP does not.
  • NAT-T wraps ESP in UDP port 4500, which PAT translates like any other UDP flow. On the NAT box you see two ordinary udp translations, :500 and :4500, and that is the entire trick.
  • You do not configure the port. IOS auto-detects the NAT during IKE using NAT-D payloads and floats to 4500 on its own. Confirm it with Peer: ... port 4500 and Capabilities:N.
  • The proof string is in use settings ={Tunnel UDP-Encaps, }. Also expect local crypto endpt. to show the private address, because the router genuinely does not know it is being translated.
  • Mind the 8 bytes. The extra UDP header eats MTU. Adjust TCP MSS and tunnel MTU rather than letting the path fragment.
  • Enable NAT keepalives so idle PAT entries do not age out and blackhole the tunnel.
  • Permit UDP 500 AND UDP 4500. Opening only 500 produces the identical Phase-1-up, Phase-2-dead symptom and wastes hours.
  • AH is out. It authenticates the outer IP header that NAT rewrites, so it can never traverse a NAT. NAT-T is an ESP-only story.

NAT-T is one of the pieces that makes real-world IPsec deployable at all, alongside crypto maps, tunnel interfaces and the failure modes that come with them. For the full picture, start at the IPsec VPN pillar, build the baseline tunnel with site-to-site IPsec crypto maps, and keep the IPsec troubleshooting guide open next to the console.

Written by
More from Ping Labz
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Ping Labz.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.