ASA

Cisco ASA ACL Configuration: Inbound Rules and Object Groups

Cisco ASA ACL Configuration: Inbound Rules and Object Groups
In: ASA

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-NAME

Note 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 80

Or, 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 outside

The 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 typeContentsExample
networkIP addresses, subnets, ranges, network-objectsGroup of all DMZ servers
serviceTCP/UDP ports, port ranges, ICMP typesWeb ports (80, 443, 8080), ICMP types
protocolIP 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 outside

That 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) 0x2cc9dc3f

This 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-interface is configured.

This means out of the box, with three interfaces (inside 100, dmz 50, outside 0):

FromToDefault
insideoutsideAllow
insidedmzAllow
dmzoutsideAllow
outsideinsideDeny
outsidedmzDeny
dmzinsideDeny

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.

Get the Cisco ASA cheat-sheet

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 warnings

Logging 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. The access-list command alone does nothing. The ACL must be applied with access-group to be evaluated.
  • Wrong direction. Always use in interface unless 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 any at 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.

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.

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.