What breaks when you migrate ASA NAT to PAN-OS
Conversion tools translate the syntax. The failures that actually page you at 2 a.m. come from how the two platforms evaluate NAT against security policy — and they don't show up until traffic is flowing.
If you've done an ASA-to-Palo Alto migration, you know the security rules are the easy part. Access-lists map to security rules fairly cleanly. It's NAT — and specifically the interaction between NAT and security policy — where a converted config that looks perfect silently drops or over-permits traffic once it's live.
The root cause is a single difference in evaluation order that trips up even experienced engineers. Here's the trap, worked through end to end, and how to verify you didn't fall into it before the change window closes.
The one rule that explains most NAT migration bugs
PAN-OS evaluates a security rule against the pre-NAT address but the post-NAT zone.
Read that twice, because the asymmetry is the whole problem. When a packet arrives, the firewall does a route lookup on the translated destination to decide the egress zone — so the destination zone in your security rule is where the real server lives. But the address it matches in that same rule is the original, untranslated address the packet arrived with.
Miss either half and you get a rule that matches nothing (traffic dropped) or matches too much (traffic allowed you didn't intend).
The inbound DNAT trap, worked
Take a public web server. On the ASA (8.3 or later), you have a static NAT and an access-list permitting traffic to the real IP — because 8.3+ ACLs reference the real address, not the mapped one:
! Cisco ASA (8.3+)
object network WEB_SERVER
host 10.1.1.100
object network WEB_PUBLIC
host 203.0.113.10
object network WEB_SERVER
nat (inside,outside) static WEB_PUBLIC
!
access-list OUTSIDE_IN permit tcp any object WEB_SERVER eq https
The ACL permits traffic to WEB_SERVER — the real 10.1.1.100.
Now the PAN-OS equivalent. You need two rules — a NAT rule and a security rule — and they reference the addresses and zones differently. This is where it goes wrong.
# PAN-OS — NAT rule (matches on the ORIGINAL packet)
set rulebase nat rules Inbound-Web from untrust to untrust \
destination 203.0.113.10 service service-https \
destination-translation translated-address 10.1.1.100
NAT rule: from/to zones are both untrust — the pre-NAT zones the original packet routes through.
# PAN-OS — security rule
set rulebase security rules Allow-Web from untrust to trust \
destination 203.0.113.10 application ssl service application-default \
action allow
Security rule: to-zone is trust (post-NAT, where 10.1.1.100 lives), but destination is 203.0.113.10 (pre-NAT, the public IP).
Lay the two rules side by side and the asymmetry is obvious:
| Field | NAT rule | Security rule |
|---|---|---|
| Destination zone | untrust (pre-NAT) | trust (post-NAT) |
| Destination address | 203.0.113.10 (public) | 203.0.113.10 (public) |
| Translated to | 10.1.1.100 (private) | — |
Either they set the security rule's destination to the private IP (10.1.1.100) — matching pre-NAT address is the rule, so it never matches and the server is unreachable — or they set the to-zone to untrust — the packet's already been routed to trust, so again no match. Both produce a config that passes a visual review and fails in production.
Why auto vs. manual NAT doesn't map cleanly
ASA has three NAT sections evaluated in order: manual (twice) NAT in section 1, auto (object) NAT in section 2, and after-auto manual NAT in section 3. PAN-OS has one ordered NAT rulebase, top-down, first match wins.
A naive conversion that dumps every ASA NAT into a flat PAN-OS list in the wrong order changes which translation applies to a given flow. Object NAT that ASA quietly evaluated after your twice-NAT can end up matching first in PAN-OS, silently translating traffic you meant to exempt. The fix is to preserve ASA's section precedence when flattening — not just convert each rule in isolation.
The failure you can't see: orphaned NAT
Here's the one that survives review and haunts you later. After a migration you frequently end up with a NAT rule that has no security rule permitting the translated traffic — an orphan. The translation is configured, the packet gets rewritten, and then it's dropped by the implicit deny because nothing allows it. Or the inverse: a security rule referencing an address that NAT never produces, permitting nothing.
On a small config you'd catch it by eye. On a real one — say 135 NAT rules against 365 security rules — you won't. The only reliable way is to correlate the two rulebases: for every NAT rule, which security rules actually pass that traffic (matched on pre-NAT addresses), and for every security rule, is there a NAT path that feeds it? Any NAT rule with zero security matches is an orphan worth knowing about before you commit, not after.
Before you push to Panorama: (1) confirm every inbound security rule uses the pre-NAT (public) address and the post-NAT (internal) zone; (2) verify NAT rule order preserves ASA section precedence; (3) correlate NAT ↔ security policy and resolve every orphaned NAT; (4) check that port-translating statics carried their translated port into the destination-NAT rule.
Catch it in flight, not in production
The reason NAT is the graveyard of firewall migrations is that none of these failures are syntax errors — a converter that only rewrites text will happily produce all of them. They're semantic, and catching them means understanding the config as a policy graph: which addresses, which zones, which rules relate to which, resolved together.
Whether you use tooling or a careful spreadsheet, the principle is the same: a migration is the cheapest moment to catch these, because you're touching every rule anyway. Verify the pre-NAT/post-NAT-zone logic on every inbound rule, keep your NAT ordering faithful to the source, and hunt down the orphans before the change window closes. Don't carry the gaps across.
Written by Roman Dmytriv — network security engineer (CISSP), two decades securing and modernizing enterprise networks. More writing at blog.dmytriv.com, and I'm on LinkedIn.