ASA

Cisco ASA ACL Troubleshooting with packet-tracer

Cisco ASA ACL Troubleshooting with packet-tracer
In: ASA

Most "the ACL is broken" tickets are not really broken ACLs. They are misunderstood ACLs: a packet you thought matched line 3 actually matched line 1 and got denied; a deny line further down absorbed traffic the user expected to permit; an implicit deny at the end caught a flow nobody had a permit for. The ASA always tells you exactly what happened, but you have to ask it the right way.

This article is the ACL troubleshooting reference for the Cisco ASA on software 9.x. The diagnostic of choice is packet-tracer, backed by show access-list hit counters and show asp drop. All output below is from a live ASAv in the PingLabz reference lab. For the cluster context, see the Cisco ASA pillar; for the broader ACL guide, see Cisco ASA ACL Configuration; for the packet-tracer command itself, see Cisco ASA packet-tracer Command.

The Three Tools, in the Order You Use Them

StepCommandTells you
1packet-tracer input ...For one specific 5-tuple, did the ACL allow or drop, and which line matched.
2show access-list NAMEPer-line hit counters and last-hit timestamps for the named ACL.
3show asp dropAggregate counter of every drop reason on the ASA, including acl-drop.

Step 1 answers "what happens to this exact packet?" Step 2 answers "is the rule I just edited actually being hit?" Step 3 answers "is the ASA dropping anything, and if so, why?"

Lab Setup

The PingLabz ASA has an OUTSIDE_IN ACL applied inbound on the outside interface. The ACL has been deliberately seeded with one bad-actor block at the top, three permits for the DMZ web server, and an explicit catch-all deny with logging at the bottom (added in addition to the implicit deny so we can see clean logging in syslog):

ASA-PERIM# show running-config access-list
access-list OUTSIDE_IN extended deny tcp host 198.51.100.99 any
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq www
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq https
access-list OUTSIDE_IN extended permit icmp any object DMZ-WEB
access-list OUTSIDE_IN extended deny ip any any log

That gives us five different troubleshooting scenarios on one ACL: an explicit deny hit, three permit lines (one cold, two warm), and a logged catch-all deny.

Scenario 1: Explicit Deny Hit

An attacker IP we have blocklisted (198.51.100.99) tries to reach the DMZ web server. We expect line 1 of the ACL to drop it.

ASA-PERIM# packet-tracer input outside tcp 198.51.100.99 51000 198.51.100.10 443

Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Config:
object network DMZ-WEB
 nat (dmz,outside) static 198.51.100.10
Additional Information:
NAT divert to egress interface dmz
Untranslate 198.51.100.10/443 to 192.168.50.10/443

Phase: 2
Type: ACCESS-LIST
Subtype:
Result: DROP
Config:
access-group OUTSIDE_IN in interface outside
access-list OUTSIDE_IN extended deny tcp host 198.51.100.99 any

Result:
input-interface: outside
input-status: up
input-line-status: up
output-interface: dmz
output-status: up
output-line-status: up
Action: drop
Time Taken: 52927 ns
Drop-reason: (acl-drop) Flow is denied by configured rule, Drop-location: frame snp_classify_table_lookup:6044 flow (NA)/NA

Phase 2 is where the answer lives. The drop reason is acl-drop, the line that fired is printed verbatim (access-list OUTSIDE_IN extended deny tcp host 198.51.100.99 any), and the ACL name is shown via the access-group binding. No guessing required.

One detail worth noting: NAT (Phase 1) ran before the ACL (Phase 2). The packet-tracer output shows the ACL evaluating against the post-NAT (real) destination 192.168.50.10, which is why the DMZ-WEB object reference works in the permit lines. On modern ASA software the ACL always sees real addresses on both sides of the rule.

Scenario 2: Permit Hit (with a Lab Quirk at the End)

A normal user IP reaches DMZ-WEB on TCP/443. We expect line 3 (the HTTPS permit) to match.

ASA-PERIM# packet-tracer input outside tcp 198.51.100.50 51001 198.51.100.10 443

Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Config:
object network DMZ-WEB
 nat (dmz,outside) static 198.51.100.10
Additional Information:
NAT divert to egress interface dmz
Untranslate 198.51.100.10/443 to 192.168.50.10/443

Phase: 2
Type: ACCESS-LIST
Subtype:
Result: ALLOW
Config:
access-group OUTSIDE_IN in interface outside

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

Phase: 11
Type: INPUT-ROUTE-LOOKUP-FROM-OUTPUT-ROUTE-LOOKUP
Subtype: Resolve Preferred Egress interface
Result: ALLOW
Found next-hop 192.168.50.10 using egress ifc  dmz

Result:
Action: drop
Drop-reason: (no-v4-adjacency) No valid V4 adjacency. Check ARP table (show arp) has entry for nexthop., Drop-location: frame snp_fp_adj_process_cb:256 flow (NA)/NA

Phase 2 says ALLOW. The ACL did its job. The eventual Action: drop at the bottom is from no-v4-adjacency, which is a downstream problem (no ARP entry for the DMZ host in this lab), not an ACL problem.

This is the exact pattern you will hit in production all the time: the ACL is fine, the failure is somewhere else. Reading the packet-tracer in order, top-to-bottom, prevents you from blaming the ACL when the cause is one phase later.

Scenario 3: The Explicit Catch-All Deny

A legitimate user IP tries to reach DMZ-WEB on TCP/22 (SSH), which the published service does not expose. We expect line 5 (the explicit deny ip any any log) to fire.

ASA-PERIM# packet-tracer input outside tcp 198.51.100.50 51002 198.51.100.10 22

Phase: 1
Type: UN-NAT
Subtype: static
Result: ALLOW
Config:
object network DMZ-WEB
 nat (dmz,outside) static 198.51.100.10
Additional Information:
NAT divert to egress interface dmz
Untranslate 198.51.100.10/22 to 192.168.50.10/22

Phase: 2
Type: ACCESS-LIST
Subtype: log
Result: DROP
Config:
access-group OUTSIDE_IN in interface outside
access-list OUTSIDE_IN extended deny ip any any log

Result:
Action: drop
Drop-reason: (acl-drop) Flow is denied by configured rule, Drop-location: frame snp_classify_table_lookup:6044 flow (NA)/NA

Phase 2 says DROP, and now the Subtype: log field is populated because the matched line has the log keyword. The line that fired is the catch-all deny ip any any log, and the corresponding syslog message gets generated (%ASA-4-106023) for the security team.

Lesson: if you want visibility into what is being dropped at the implicit deny, replace it with an explicit deny with the log keyword. The ASA's implicit deny does not log.

Reading Hit Counters

After running the three packet-tracer scenarios, show access-list OUTSIDE_IN shows the per-line counters with timestamps:

ASA-PERIM# show access-list OUTSIDE_IN
access-list OUTSIDE_IN; 5 elements; name hash: 0xe01d8199
access-list OUTSIDE_IN line 1 extended deny tcp host 198.51.100.99 any (hitcnt=1) (Last Hit=00:01:11 UTC May 10 2026) 0xa0fb08eb
access-list OUTSIDE_IN line 2 extended permit tcp any object DMZ-WEB eq www (hitcnt=1) (Last Hit=23:27:43 UTC May 9 2026) 0xf18a0028
  access-list OUTSIDE_IN line 2 extended permit tcp any host 192.168.50.10 eq www (hitcnt=1) (Last Hit=23:27:43 UTC May 9 2026) 0xf18a0028
access-list OUTSIDE_IN line 3 extended permit tcp any object DMZ-WEB eq https (hitcnt=2) (Last Hit=00:01:11 UTC May 10 2026) 0x1ce50e7b
  access-list OUTSIDE_IN line 3 extended permit tcp any host 192.168.50.10 eq https (hitcnt=2) (Last Hit=00:01:11 UTC May 10 2026) 0x1ce50e7b
access-list OUTSIDE_IN line 4 extended permit icmp any object DMZ-WEB (hitcnt=0) 0x2cc9dc3f
  access-list OUTSIDE_IN line 4 extended permit icmp any host 192.168.50.10 (hitcnt=0) 0x2cc9dc3f
access-list OUTSIDE_IN line 5 extended deny ip any any log informational interval 300 (hitcnt=1) (Last Hit=00:01:12 UTC May 10 2026) 0x2dc51227

Read off the data:

  • Line 1 (deny attacker): hitcnt=1. The explicit deny scenario ran exactly once.
  • Line 2 (permit www): hitcnt=1. From an earlier test session.
  • Line 3 (permit https): hitcnt=2. Two HTTPS allow runs.
  • Line 4 (permit icmp): hitcnt=0. Never been hit. This is informational: maybe the rule was added preemptively, maybe it should be removed, but it is not failing.
  • Line 5 (catchall deny): hitcnt=1. The TCP/22 drop scenario.

Each "object" line in the show output expands to its underlying real address ("host 192.168.50.10") on the next line, with the same hit count. That is how object-group expansion is rendered in the show output - one logical line, one or more underlying lines.

The real diagnostic value of show access-list is the (hitcnt=...) column. After every change to an ACL, run a few real connections, then check the counter on the line you expected to fire. If the counter does not increment, your rule is not matching. The most common reasons are wrong source/destination, wrong port, or another line above absorbing the traffic.

The Big Picture: show asp drop

For aggregate visibility into everything the ASA has dropped, ACL or otherwise, use show asp drop:

ASA-PERIM# show asp drop frame acl-drop
  Flow is denied by configured rule (acl-drop)                               317

Last clearing: Never

317 ACL drops since the ASA last had its counters cleared. The frame acl-drop argument filters to just the acl-drop reason; without arguments you get the full table:

ASA-PERIM# show asp drop

Frame drop:
  No valid V4 adjacency. Check ARP table (show arp) has entry for nexthop. (no-v4-adjacency)                    3
  Flow is denied by configured rule (acl-drop)                               305
  FP L2 rule drop (l2_acl)                                                    16
  Interface is down (interface-down)                                           3

Last clearing: Never

This is the place to look when a user reports "my packet is being dropped somewhere on the firewall" but cannot say where. The drop-reason names map one-to-one with what packet-tracer prints in its Result section, so once you know the reason you can run a packet-tracer and confirm.

Useful asp drop reasons related to ACLs and policy:

Drop reasonWhat it means
acl-dropInterface ACL denied the flow.
l2_aclLayer-2 ACL denied (Ethertype, MAC).
nat-no-xlate-to-pat-poolPAT pool exhausted; no port available.
no-v4-adjacencyNo ARP entry for the next-hop on the egress interface.
rpf-violateduRPF check failed; source routes back through a different interface.
tcp-not-synTCP packet without SYN arrived for a flow that does not exist; usually a stale flow on the client.

If acl-drop is climbing without a known cause, run capture asp_drop type asp-drop acl-drop to capture the actual dropped frames for analysis. That is the deepest level of ACL diagnostic the ASA offers.

Common ACL Bugs and How to Spot Them

  • An earlier deny absorbs the permit. Symptom: the permit line is in the config but its hit count is zero. Diagnosis: run packet-tracer; phase 2 will print the actual line that fired. Fix: re-order the ACL.
  • Object expansion mismatch. The permit references an object-group, but the user's traffic does not match any member of the group. Diagnosis: show access-list expands the group inline; compare members to the actual source/destination. Fix: add the missing member to the object-group.
  • ACL applied to the wrong interface or direction. Symptom: ACL exists but never matches. Diagnosis: show running-config access-group tells you what is bound where. Fix: re-bind correctly.
  • Real vs mapped IP confusion (legacy configs). ACLs from ASA software pre-8.3 used mapped IPs; modern ASA uses real IPs. Symptom: traffic works on an old ASA but breaks after upgrade. Diagnosis: packet-tracer shows the rule does not match the real address. Fix: rewrite the ACL to reference real IPs.
  • Implicit deny silently dropping. Symptom: traffic just disappears, no syslog. Diagnosis: nothing in show access-list matches. Fix: replace the implicit deny with an explicit deny ip any any log at the bottom of every ACL so future drops generate syslog.

The Five-Step ACL Troubleshooting Checklist

  1. Run packet-tracer input INTERFACE PROTO SRC SPORT DST DPORT with the exact 5-tuple from the user complaint. Read every phase top-to-bottom.
  2. If phase 2 (ACCESS-LIST) is the drop, the printed line tells you the rule that fired. Compare to the rule you expected.
  3. If the drop is somewhere else (NAT, route-lookup, adjacency), the ACL is not the problem. Stop blaming it.
  4. If phase 2 says ALLOW but the user still cannot connect, run a real test connection and check show access-list hit counters. Counter incrementing means the ACL is letting it through; the failure is downstream.
  5. For periodic auditing, run show asp drop to see aggregate drop counters by reason. Sudden spikes in acl-drop with no known cause warrant capture asp_drop.

Where to Go Next

Key Takeaways

  • ACL troubleshooting on the ASA is three commands: packet-tracer for one specific flow, show access-list for per-line hit counters, show asp drop for aggregate drop reasons.
  • packet-tracer phase 2 prints the exact ACL line that matched, in either ALLOW or DROP. Read it before guessing.
  • The implicit deny does not log. Replace it with an explicit deny ip any any log at the bottom of every interface ACL so silent drops become visible.
  • Modern ASA ACLs reference real (untranslated) addresses. Configs ported from pre-8.3 ASAs that still reference mapped IPs will fail after upgrade; rewrite them to real IPs.
  • If packet-tracer's phase 2 ALLOWs but the user still cannot connect, the failure is downstream of the ACL. Stop debugging the ACL.
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.