ACLs on the Cisco ASA work the same as on IOS routers in spirit but with three differences that matter in practice: ASA ACLs are almost always applied inbound, they reference the real (untranslated) destination IP rather than the post-NAT mapped IP, and object groups are heavily used to keep them readable. This article walks the full ASA ACL model, the syntax, the object-group pattern, and the common mistakes engineers coming from IOS hit on day one.
It is part of the Cisco ASA Complete Guide on PingLabz. Adjacent reads: Cisco ASA Packet Flow for where the ACL fits in the data path, ASA vs Router ACLs for the IOS-engineer perspective, and ACL Troubleshooting with packet-tracer for incident response.
ASA ACL Basics
An ASA ACL is a named, ordered list of permit/deny rules applied to an interface in the inbound direction. Rules are evaluated top to bottom, first match wins, with an implicit deny ip any any at the end. The structure:
access-list NAME extended {permit | deny} protocol src dst [eq port]
access-group NAME in interface INTERFACE-NAMENote extended in the syntax. ASA also supports standard, ethertype, and webtype ACLs but extended is what 99% of engineers configure. extended means "matches on protocol, source, destination, and port" - the fully featured ACL type.
The Real-IP Rule: ASA ACLs Match Untranslated Destinations
This is the single most-confusing detail for engineers coming from older ASA software (pre-8.3) or from IOS routers. On modern ASA, the inbound ACL on an interface evaluates the real (post-untranslate) destination, not the mapped (public) destination.
Concretely: a DMZ web server at 192.168.50.10 is published as 198.51.100.10. The packet arrives on the outside interface destined for 198.51.100.10. The ASA's UN-NAT phase reverses the static NAT and now sees 192.168.50.10 as the destination. Then the ACL evaluates against 192.168.50.10. Your OUTSIDE_IN ACL must permit traffic to 192.168.50.10, not 198.51.100.10.
! WRONG (old ASA pre-8.3 mental model)
access-list OUTSIDE_IN extended permit tcp any host 198.51.100.10 eq 80
! RIGHT (modern ASA real-IP rule)
access-list OUTSIDE_IN extended permit tcp any host 192.168.50.10 eq 80Or, with object groups (the recommended pattern):
object network DMZ-WEB
host 192.168.50.10
nat (dmz,outside) static 198.51.100.10
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq 80
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq 443
access-group OUTSIDE_IN in interface outsideThe object DMZ-WEB reference resolves to 192.168.50.10 (the host-object value), so the rule is internally consistent and the public IP shows up only once, in the NAT statement. This is the pattern modern ASA configurations should use everywhere.
Object Groups: Keep ACLs Readable
Object groups bundle related addresses, ports, or protocols into a single named group. They are the difference between a 5-rule ACL and a 50-rule ACL doing the same thing.
Three types matter most:
| Object-group type | Contents | Example |
|---|---|---|
| network | IP addresses, subnets, ranges, network-objects | Group of all DMZ servers |
| service | TCP/UDP ports, port ranges, ICMP types | Web ports (80, 443, 8080), ICMP types |
| protocol | IP protocols (tcp, udp, icmp, gre, esp) | VPN protocols |
A typical compact rule using all three:
object-group network DMZ-WEB-SERVERS
network-object host 192.168.50.10
network-object host 192.168.50.11
network-object host 192.168.50.12
object-group service WEB-PORTS tcp
port-object eq 80
port-object eq 443
port-object eq 8080
access-list OUTSIDE_IN extended permit tcp any object-group DMZ-WEB-SERVERS object-group WEB-PORTS
access-group OUTSIDE_IN in interface outsideThat single rule expands internally into 9 logical rules (3 servers × 3 ports), but you maintain one line. Add a server, add a port - the change is in one place. Engineers come back to ACLs months later and the intent is still legible.
Verifying with show access-list
show access-list NAME prints the ACL with per-line hit counters. This is how you find which rules are actually firing and which are dead.
ASA-PERIM# show access-list OUTSIDE_IN
access-list OUTSIDE_IN; 3 elements; name hash: 0xe01d8199
access-list OUTSIDE_IN line 1 extended permit tcp any object DMZ-WEB eq www (hitcnt=234) 0xf18a0028
access-list OUTSIDE_IN line 1 extended permit tcp any host 192.168.50.10 eq www (hitcnt=234) 0xf18a0028
access-list OUTSIDE_IN line 2 extended permit tcp any object DMZ-WEB eq https (hitcnt=4521) 0x1ce50e7b
access-list OUTSIDE_IN line 2 extended permit tcp any host 192.168.50.10 eq https (hitcnt=4521) 0x1ce50e7b
access-list OUTSIDE_IN line 3 extended permit icmp any object DMZ-WEB (hitcnt=12) 0x2cc9dc3f
access-list OUTSIDE_IN line 3 extended permit icmp any host 192.168.50.10 (hitcnt=12) 0x2cc9dc3fThis is real output from our ASAv 9.23 lab. Each rule that uses a network object prints twice: the configured form (with the object name) and the resolved form (with the object's actual address). That nested view is helpful when an object has multiple network-object members - you can confirm each expansion. 4521 hits on the HTTPS line, 234 on HTTP, 12 on ICMP. If a rule has zero hits over weeks, it is either redundant or never matches - candidate for cleanup. To reset the counters: clear access-list OUTSIDE_IN counters.
Direction: Almost Always Inbound
ASA ACLs are typically applied inbound on the interface where the traffic enters. Outbound ACLs exist but are rare; their use case is filtering specific egress traffic regardless of source interface, and most security models prefer to filter at ingress where the trust level is lowest.
The convention follows: an ACL named OUTSIDE_IN is applied inbound on the outside interface. INSIDE_IN is applied inbound on inside, etc. Some shops use OUTSIDE_ACL with no direction in the name; both conventions are fine but consistency within an environment matters more than which one you pick.
Default Behavior Without an ACL
If no ACL is applied to an interface, the ASA uses its security-level defaults:
- Higher-security to lower-security: allow.
- Lower-security to higher-security: deny.
- Same-security to same-security: deny unless
same-security-traffic permit inter-interfaceis configured.
This means out of the box, with three interfaces (inside 100, dmz 50, outside 0):
| From | To | Default |
|---|---|---|
| inside | outside | Allow |
| inside | dmz | Allow |
| dmz | outside | Allow |
| outside | inside | Deny |
| outside | dmz | Deny |
| dmz | inside | Deny |
The implications: outbound (inside-to-outside) typically does not need an explicit ACL because the implicit allow handles it. Inbound (outside-to-inside or outside-to-dmz) almost always does need an ACL to permit anything to traverse. We cover this in detail in Cisco ASA Security Levels Explained: Inside, Outside, DMZ.
Get the Cisco ASA Field Reference - 9 pages, free
Everything you'd want to remember about Cisco ASA on nine printable pages. Per-packet pipeline diagram, NAT 8.3+ section ordering, six-branch troubleshooting decision tree, real lab show-output annotated, paste-ready three-zone config. Free for PingLabz members - just sign up with your email.
Logging: Inline and Per-Rule
Add log at the end of any ACL line to log every match to syslog at the level you specify (default is informational, level 6):
access-list OUTSIDE_IN extended permit tcp any object DMZ-WEB eq 443 log
access-list OUTSIDE_IN extended deny ip any any log warningsLogging every permit is expensive at scale; logging the implicit deny (or an explicit deny-all at the end) is cheap and very useful for incident triage and security event review. Send these to a SIEM via syslog configuration.
Common Mistakes
- Writing the ACL against the public IP. Use the real IP. Run packet-tracer once and read the UN-NAT phase to confirm what the ACL will compare.
- Forgetting
access-group ... in interface. Theaccess-listcommand alone does nothing. The ACL must be applied withaccess-groupto be evaluated. - Wrong direction. Always use
in interfaceunless you have a specific reason to filter outbound. Outbound ACLs are correct sometimes but they confuse the next engineer. - Implicit deny surprises. When you add a single permit line to an empty ACL, the implicit deny suddenly applies to everything else. If your goal was to add one allow without breaking the wide-open policy, you needed a
permit ip any anyat the end first. - ACL on the wrong interface. Inbound traffic to a DMZ server enters on the outside interface, not the dmz interface. Apply the ACL on outside.
- Using ACL line numbers without explicit insertion. Modern ASA software lets you insert at a position with
access-list OUTSIDE_IN line 3 extended .... Without the line number, new rules are appended at the end - which can put them after a deny and make them dead. - Not using object groups. A 50-line ACL with repeated host addresses is unmaintainable. Group them.
Related Articles
- Cisco ASA ACL Troubleshooting with packet-tracer - the incident-response playbook.
- Cisco ASA Object Groups: Network, Service, and Protocol Objects - the deep dive on the bundling syntax.
- ASA vs Router ACLs: What Network Engineers Get Wrong - the IOS-engineer comparison.
- Cisco ASA Security Levels Explained - default behavior context.
- Cisco ASA Packet Flow - where the ACL sits in the data path.
- Cisco ASA Cheat Sheet - quick command reference.
Key Takeaways
ASA ACLs are inbound, named, ordered lists with an implicit deny at the end. They reference real (untranslated) destination IPs, which is the single biggest gotcha for engineers used to old ASA or to IOS routers. Object groups make ACLs maintainable by bundling addresses and ports under named references. Default security-level behavior handles outbound (high-to-low) for free; inbound (low-to-high) almost always requires an explicit ACL. Use show access-list to verify hit counts and find dead rules, and packet-tracer to confirm the ACL is doing what you expect.