ASA

Cisco ASA Identity NAT / NAT Exemption for VPNs

Cisco ASA Identity NAT / NAT Exemption for VPNs
In: ASA

Identity NAT, sometimes called NAT exemption or "no-NAT", is the rule you write when you specifically do not want to translate a flow. The most common reason: site-to-site IPsec or remote-access VPN traffic. The remote VPN peer expects to see the original inside addresses, and any source NAT in the path corrupts the SA selectors. The fix is to write a rule that matches the VPN-bound flow and translates it to itself - the same address in, the same address out.

This article walks identity NAT on Cisco ASA software 9.x, with real captures from the PingLabz reference lab. For the cluster context, see the Cisco ASA pillar; for the broader NAT picture, see Cisco ASA NAT Explained; for the two NAT types we are bypassing, see Dynamic PAT and Twice NAT.

Why VPN Traffic Needs to Skip NAT

Two reasons engineers care about identity NAT, both rooted in how IPsec and remote-access VPN work.

IPsec selectors are absolute. A site-to-site IPsec tunnel is defined by a crypto map ACL: traffic matching the ACL is encrypted, traffic that does not match goes in the clear. The matching is done on real (untranslated) addresses by default. If the ASA also has a dynamic PAT rule that fires before the crypto match, the source IP is rewritten to the outside interface, the crypto ACL no longer matches, and the packet leaves the ASA in the clear instead of in the tunnel. The remote peer then drops it because it expected encrypted traffic for that flow.

Remote-access VPN clients expect to see the inside subnets unchanged. When an AnyConnect or Cisco Secure Client user from 10.99.99.0/24 reaches an inside server at 10.10.10.50, the server replies to 10.99.99.0/24. If the ASA NATs the source on the way back, the client never sees the reply because the source IP no longer matches the local route table on the client.

In both cases, the answer is the same: write an explicit identity NAT rule for VPN-bound traffic so the inside addresses arrive at the tunnel unchanged.

Identity NAT is implemented as manual NAT (twice NAT) where source and destination both translate to themselves. It lives in Section 1 of the NAT table, which is evaluated before Section 2 auto NAT. That ordering is what makes the exemption work: the identity rule wins over the dynamic PAT rule that would otherwise rewrite the source.

The Lab Topology and the Goal

All output below is from a live ASAv running Cisco ASA 9.23(1). The relevant pieces:

  • Inside corporate space: 10.10.0.0/16 (covers all current and future inside subnets).
  • Outbound default: dynamic PAT to the outside interface (Section 2 auto NAT) for everything to the internet.
  • Remote-access VPN pool: 10.99.99.0/24 (assigned to AnyConnect / Secure Client users).

The goal: when an inside host (anything in 10.10.0.0/16) talks to a VPN client (10.99.99.0/24), do not translate either address. For all other destinations, normal PAT applies.

The Identity NAT Rule

Two network objects (the inside aggregate and the VPN pool) plus one manual NAT rule:

object network INSIDE-NET-FULL
 subnet 10.10.0.0 255.255.0.0

object network REMOTE-VPN-NET
 subnet 10.99.99.0 255.255.255.0

nat (inside,outside) source static INSIDE-NET-FULL INSIDE-NET-FULL destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup

Read it carefully:

  • (inside,outside) - real interface, mapped interface. Outbound traffic from inside arrives here.
  • source static INSIDE-NET-FULL INSIDE-NET-FULL - match source against object INSIDE-NET-FULL (10.10.0.0/16), translate to the same object. Same object on both sides means identity, no rewrite.
  • destination static REMOTE-VPN-NET REMOTE-VPN-NET - match destination against the VPN pool, do not translate. Same convention.
  • no-proxy-arp - the ASA must not answer ARP for the inside addresses on the outside interface. Default would have the ASA proxy-ARP for the entire inside aggregate, which leaks ARP traffic to the upstream and breaks the network. Always include no-proxy-arp on identity NAT.
  • route-lookup - the ASA does a route lookup to determine egress interface, instead of using the rule's mapped interface. Required when the destination is reachable through a different interface than the rule names (which is the VPN case: the destination address belongs to the VPN tunnel, not literally to "outside").

The two flags at the end are not optional in practice. Skip no-proxy-arp and you will probably break something in production. Skip route-lookup and the ASA will try to forward the VPN-bound packet directly out the named outside interface, miss the tunnel, and either drop or send in the clear.

Verifying the Rule Loaded

show running-config nat shows the rule at the top, in Section 1:

ASA-PERIM# show running-config nat
nat (inside,outside) source static INSIDE-NET-FULL INSIDE-NET-FULL destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup
nat (inside,outside) source dynamic INSIDE-NET INSIDE-PARTNER-PAT destination static PARTNER-NET PARTNER-NET
!
object network INSIDE-NET
 nat (inside,outside) dynamic interface
object network INSIDE-TRANSIT
 nat (inside,outside) dynamic interface
object network DMZ-WEB
 nat (dmz,outside) static 198.51.100.10

show nat detail shows what the ASA actually built from that rule:

ASA-PERIM# show nat detail

Manual NAT Policies (Section 1)
1 (inside) to (outside) source static INSIDE-NET-FULL INSIDE-NET-FULL  destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup
    translate_hits = 1, untranslate_hits = 1
    Source - Origin: 10.10.0.0/16, Translated: 10.10.0.0/16
    Destination - Origin: 10.99.99.0/24, Translated: 10.99.99.0/24
2 (inside) to (outside) source dynamic INSIDE-NET INSIDE-PARTNER-PAT  destination static PARTNER-NET PARTNER-NET
    translate_hits = 1, untranslate_hits = 1
    Source - Origin: 10.10.10.0/24, Translated: 198.51.100.99/32
    Destination - Origin: 172.20.0.0/24, Translated: 172.20.0.0/24

Auto NAT Policies (Section 2)
1 (dmz) to (outside) source static DMZ-WEB 198.51.100.10
    translate_hits = 0, untranslate_hits = 7
2 (inside) to (outside) source dynamic INSIDE-TRANSIT interface
    translate_hits = 0, untranslate_hits = 0
3 (inside) to (outside) source dynamic INSIDE-NET interface
    translate_hits = 46, untranslate_hits = 0

Read the Section 1 rule 1 carefully: Source - Origin: 10.10.0.0/16, Translated: 10.10.0.0/16. Origin and translated are identical. Same on the destination side. That is what identity means.

Proving the Rule Wins Over Dynamic PAT

The whole point of identity NAT is that it should fire before the dynamic PAT rule. packet-tracer makes that visible. Send a packet from an inside host to a VPN client address:

ASA-PERIM# packet-tracer input inside tcp 10.10.10.50 33000 10.99.99.10 443

Phase: 1
Type: INPUT-ROUTE-LOOKUP
Subtype: Resolve Egress Interface
Result: ALLOW
Found next-hop 203.0.113.1 using egress ifc  outside

Phase: 2
Type: UN-NAT
Subtype: static
Result: ALLOW
Config:
nat (inside,outside) source static INSIDE-NET-FULL INSIDE-NET-FULL destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup
Additional Information:
NAT divert to egress interface outside
Untranslate 10.99.99.10/443 to 10.99.99.10/443

Phase: 3
Type: NAT
Subtype:
Result: ALLOW
Config:
nat (inside,outside) source static INSIDE-NET-FULL INSIDE-NET-FULL destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup
Additional Information:
Static translate 10.10.10.50/33000 to 10.10.10.50/33000

Phase: 7
Type: NAT
Subtype: rpf-check
Result: ALLOW
Config:
nat (inside,outside) source static INSIDE-NET-FULL INSIDE-NET-FULL destination static REMOTE-VPN-NET REMOTE-VPN-NET no-proxy-arp route-lookup

Phase: 11
Type: FLOW-CREATION
Subtype:
Result: ALLOW
New flow created with id 52, packet dispatched to next module

Result:
input-interface: inside
input-status: up
input-line-status: up
output-interface: outside
output-status: up
output-line-status: up
Action: allow

The two NAT phases are the proof:

  • Phase 2 (UN-NAT): Untranslate 10.99.99.10/443 to 10.99.99.10/443 - destination matched against the identity rule, no rewrite.
  • Phase 3 (NAT): Static translate 10.10.10.50/33000 to 10.10.10.50/33000 - source matched against the identity rule, no rewrite.

Both phases reference the manual NAT rule. The dynamic PAT rule (Section 2) never gets evaluated because Section 1 already produced a match. That is the entire mechanism: by virtue of being in Section 1, identity NAT pre-empts the dynamic rule.

Compare to a packet from the same source going to the actual internet (8.8.8.8), which should hit the auto NAT rule:

ASA-PERIM# packet-tracer input inside tcp 10.10.10.50 49000 8.8.8.8 80

Phase: 2
Type: NAT
Subtype:
Result: ALLOW
Config:
object network INSIDE-NET
 nat (inside,outside) dynamic interface
Additional Information:
Dynamic translate 10.10.10.50/49000 to 203.0.113.2/49000

Same source, different destination, different rule. The Section 1 identity rule does not match because 8.8.8.8 is not in REMOTE-VPN-NET, so evaluation continues into Section 2 where the dynamic PAT rule wins. Source rewrites to the outside interface IP, exactly as it should. Identity NAT only fires when the destination is VPN-relevant.

When Identity NAT Is Required

Reach for identity NAT when one of these is true:

  • Site-to-site IPsec tunnel between this ASA and a remote peer where the inside addresses must arrive at the tunnel unchanged. The crypto ACL on both sides matches real addresses.
  • Remote-access VPN (AnyConnect, Cisco Secure Client) where users in the VPN pool need to reach inside servers and the inside servers' replies must reach the VPN clients with the original IPs intact.
  • Reaching a partner network through a tunnel where the partner expects your real corporate addresses and has provisioned routing on their side accordingly.
  • Carving an exception out of a broader auto NAT rule. If your default outbound rule PATs the entire inside, and one specific destination subnet should bypass NAT, an identity NAT rule in Section 1 is the right escape hatch.

Failure Modes You Will Actually See

  • Forgetting no-proxy-arp. The ASA proxy-ARPs for the entire inside aggregate on the outside interface, the upstream switch sees ARP for 10.10.0.0/16 from the ASA's outside MAC, weird forwarding loops follow. Fix: always include the flag.
  • Forgetting route-lookup. The ASA tries to forward the packet directly out the mapped interface named in the NAT rule, misses the VPN tunnel, packet leaves in the clear or gets black-holed. Fix: always include the flag for VPN-related identity NAT.
  • Identity NAT in the wrong section. Author wrote the rule as auto NAT (object NAT cannot match destination, so it does not actually express identity), thought it would work, did not. Manual NAT in Section 1 is the only way to write identity NAT for a (source, destination) pair.
  • Identity NAT with mismatched source/destination scope. The rule covers 10.10.0.0/16 but the actual inside subnet is 10.20.0.0/24. Rule never fires. Symptom: show nat detail shows zero hits for the identity rule, and inside hosts get PATted on VPN flows. Fix: align source object with actual inside addressing.
  • Order conflicts with another Section 1 rule. Two manual NAT rules both could match a flow; the wrong one wins. Fix: use the optional rule line number on the nat command to control order.

Where to Go Next

Key Takeaways

  • Identity NAT is a manual NAT rule where source (and usually destination) translate to themselves. It lives in Section 1 and pre-empts the dynamic PAT rule that would otherwise fire.
  • The classic use case is VPN: site-to-site IPsec and remote-access VPN both expect inside addresses to arrive at the tunnel untranslated.
  • Always include no-proxy-arp and, for VPN-relevant rules, route-lookup. They are not optional in practice.
  • packet-tracer is the cleanest verification: the trace will show Static translate X to X on the source phase. If you see a different translation, the identity rule is not winning.
  • Auto NAT cannot express identity for a (source, destination) pair. Identity NAT is always written as manual NAT in global config, never nested in an object.
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.