Mental model
An ACL is a checklist a router runs over every packet on a configured interface. Each line is “if the packet looks like X, permit (or deny) it.” The router walks the list top to bottom, and the first matching line wins. After that, no further lines are checked.
The trap: if no line matches, there’s an invisible last line that drops the packet. This is the implicit deny. It’s the single biggest reason “my ACL isn’t working” tickets exist — engineers forget to allow traffic they didn’t think to call out.
The one rule: Every ACL ends with
deny ip any any. You don’t type it. The router adds it.
If you write three permit lines and forget the fourth, anything that doesn’t match those three is dropped silently. Always test with the traffic you didn’t intend to block.
What ACLs are good for (and not good for)
ACLs are stateless packet filters. They look at one packet at a time — header fields only — and decide permit / deny. They don’t track connection state, don’t understand application protocols, and don’t inspect payload.
Good fits for ACLs
- Coarse network-layer segmentation (block subnet A from subnet B)
- Restricting management plane access (only NOC subnet may SSH the routers)
- Filtering routing protocol updates (control which routes get advertised)
- Marking traffic for QoS classification
- Anti-spoofing (block source IPs that shouldn’t appear on this interface)
- NAT match lists (defining which traffic to translate)
- OSPF / EIGRP route-map match conditions
Bad fits for ACLs
- Stateful inspection of TCP/UDP connections (use a firewall)
- Layer-7 application awareness (use NGFW or app proxy)
- Identity-aware policy (use 802.1X + ISE — see Cisco ISE Basics)
- High-volume east-west DC traffic policy (use micro-segmentation)
An ACL is the right hammer when the nail is “I have IP X here and want to control whether it reaches IP Y on port Z.” For everything else, look at the firewall above the router.
Standard vs Extended vs Named — the three flavors
| Standard | Extended | Named | |
|---|---|---|---|
| Filters on | Source IP only | Source + destination + protocol + port + more | Either type, with a name |
| Numbered range | 1–99 / 1300–1999 | 100–199 / 2000–2699 | (uses name instead of number) |
| Apply where | Close to destination | Close to source | Per-line editable |
| Default style in 2026 | Avoid — too coarse | OK | Best practice |
For the CCNA exam: know all three. In production: always named, because you can edit individual lines.
Numbered standard ACL
R1(config)# access-list 10 permit 192.168.1.0 0.0.0.255
R1(config)# access-list 10 deny any
R1(config)# interface Gi0/1
R1(config-if)# ip access-group 10 out
The router auto-adds the deny at the end, so the explicit deny any is technically redundant — but useful for clarity (and the explicit deny lets you add log to it).
Numbered extended ACL
R1(config)# access-list 100 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 80
R1(config)# access-list 100 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 443
R1(config)# access-list 100 deny ip any any log
R1(config)# interface Gi0/0
R1(config-if)# ip access-group 100 in
Named extended ACL (recommended)
R1(config)# ip access-list extended WEB-ONLY
R1(config-ext-nacl)# permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 80
R1(config-ext-nacl)# permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 443
R1(config-ext-nacl)# deny ip any any log
R1(config-ext-nacl)# exit
R1(config)# interface Gi0/0
R1(config-if)# ip access-group WEB-ONLY in
Editing a named ACL by line number
R1# show access-list WEB-ONLY
Extended IP access list WEB-ONLY
10 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq www (542 matches)
20 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 443 (1023 matches)
30 deny ip any any log (17 matches)
! Insert a new permit at line 15 (between 10 and 20)
R1(config)# ip access-list extended WEB-ONLY
R1(config-ext-nacl)# 15 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 22
! Remove a line
R1(config-ext-nacl)# no 15
! Renumber to clean sequence
R1(config)# ip access-list resequence WEB-ONLY 10 10
This is impossible on numbered ACLs — you’d have to delete the whole thing and re-enter. Use named.
Wildcard masks — the inverse trick
Wildcard masks are the bit-inverse of subnet masks. A 0 in the wildcard means “must match”; a 1 means “don’t care.”
Easiest conversion: subtract each subnet-mask octet from 255.
| Subnet | Wildcard | Matches |
|---|---|---|
| 255.255.255.0 (/24) | 0.0.0.255 | one /24 network |
| 255.255.255.128 (/25) | 0.0.0.127 | one /25 |
| 255.255.255.192 (/26) | 0.0.0.63 | one /26 |
| 255.255.255.224 (/27) | 0.0.0.31 | one /27 |
| 255.255.255.240 (/28) | 0.0.0.15 | one /28 |
| 255.255.255.248 (/29) | 0.0.0.7 | one /29 |
| 255.255.255.252 (/30) | 0.0.0.3 | one /30 |
| 255.255.255.255 (/32) | 0.0.0.0 | exactly one host |
| 255.255.0.0 (/16) | 0.0.255.255 | one /16 |
| 255.0.0.0 (/8) | 0.255.255.255 | one /8 |
| 0.0.0.0 (/0) | 255.255.255.255 | everything |
Special shortcuts
! Match exactly one host
permit ip host 192.168.1.5 any
! Equivalent to:
permit ip 192.168.1.5 0.0.0.0 any
! Match any source / any destination
permit ip any any
! Equivalent to:
permit ip 0.0.0.0 255.255.255.255 0.0.0.0 255.255.255.255
Wildcard masks that aren’t subnet inverses
Subnet masks are always contiguous (e.g., 11111111.11111111.11111111.00000000). Wildcard masks don’t have to be — you can use discontinuous wildcards like 0.0.0.252 (matches first 6 bits of fourth octet, ignores last 2 bits). Rare but tested on the exam.
Example: match all even IPs in 10.1.1.0/24:
permit ip 10.1.1.0 0.0.0.254 any ! matches .0, .2, .4, .6, … (all evens)
You won’t write these in real life. CCNA exam loves them.
In vs Out — the direction trap
interface GigabitEthernet0/0
ip access-group MY-ACL in ← filter packets ARRIVING on this interface
ip access-group MY-ACL out ← filter packets LEAVING this interface
Half of all real-world ACL bugs are direction mistakes, not rule mistakes. If your rule looks right but traffic still fails, swap direction and re-test before changing the rule.
Rule of thumb
- Extended ACLs → close to the source (drop early to save bandwidth on transit links).
- Standard ACLs → close to the destination (since they only filter source IP, putting them at source would block too much).
Visualizing direction
Think of yourself standing at the interface, looking at packets. in is what the interface receives. out is what the interface sends.
A common mistake: put in on the router’s WAN interface and expect to filter LAN traffic. The LAN traffic arrives on the LAN interface — it’s leaving the router on the WAN interface, so to filter LAN traffic on the WAN side, you’d use out.
The established keyword — stateless return-traffic trick
ACLs are stateless, but you can fake state for TCP using the established keyword. It matches packets that have ACK or RST flags set — i.e., packets that look like responses to an outbound connection, not new connections.
! Allow outbound TCP, allow only return traffic inbound
R1(config)# ip access-list extended OUTBOUND
R1(config-ext-nacl)# permit tcp 192.168.1.0 0.0.0.255 any
R1(config-ext-nacl)# deny ip any any
R1(config)# interface Gi0/1
R1(config-if)# ip access-group OUTBOUND in
R1(config)# ip access-list extended INBOUND
R1(config-ext-nacl)# permit tcp any 192.168.1.0 0.0.0.255 established
R1(config-ext-nacl)# deny ip any any
R1(config)# interface Gi0/0
R1(config-if)# ip access-group INBOUND in
This is the cheapest possible stateless firewall. It works for TCP only (UDP has no equivalent flag). A real firewall does this and much more — see Zone-Based Policy Firewall or any NGFW. But for CCNA-level understanding, established is the “implement basic state with ACLs” pattern.
Time-based ACLs
You can gate ACL entries by time of day or day of week:
R1(config)# time-range BUSINESS-HOURS
R1(config-time-range)# periodic weekdays 8:00 to 18:00
R1(config)# ip access-list extended LIMIT-STREAMING
R1(config-ext-nacl)# deny tcp 10.0.0.0 0.0.0.255 any eq 1935 time-range BUSINESS-HOURS
R1(config-ext-nacl)# permit ip any any
The deny only triggers during business hours. Outside that window, the line is ignored and the next line matches. Used for compliance, content filtering, or burstable bandwidth controls.
ACL match counters
Every ACL line has a hit counter:
R1# show access-list WEB-ONLY
Extended IP access list WEB-ONLY
10 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq www (1,247 matches)
20 permit tcp 10.0.0.0 0.0.0.255 host 10.2.2.10 eq 443 (5,892 matches)
30 deny ip any any log (84 matches)
The counters are your debugging gold:
- All zero = ACL isn’t being hit (wrong interface? wrong direction? wrong source/dest?).
- Hit count on the
denyline = traffic you’re blocking. Uselogkeyword for syslog details. - Hit count growing on a
permityou didn’t expect = something is matching that shouldn’t.
Reset counters:
R1# clear access-list counters WEB-ONLY
The log and log-input keywords
Append log to any ACL line and matches generate syslog entries:
deny ip any any log
log-input is stronger — also includes the source MAC + ingress interface, useful for tracking down rogue hosts:
deny ip 192.0.2.0 0.0.0.255 any log-input
Caveat: logging is CPU-intensive. Use sparingly — adding log to every line on a high-traffic ACL is a great way to brown out the router’s CPU. Use it on specific deny lines you actively monitor.
Reflexive ACLs — basic statefulness
reflexive ACLs were Cisco’s pre-firewall attempt at stateful filtering. Largely obsolete now (replaced by Zone-Based Firewall + dedicated firewalls) but show up on the CCNA blueprint.
ip access-list extended OUTBOUND
permit tcp 192.168.1.0 0.0.0.255 any reflect TCP-OUT
ip access-list extended INBOUND
evaluate TCP-OUT
deny ip any any
The reflect TCP-OUT creates dynamic entries based on outbound traffic; evaluate TCP-OUT references them. The inbound side accepts return traffic without explicit permits per session.
For CCNA: recognize the syntax; you’ll basically never deploy it new in 2026.
Verification — the four commands
R1# show access-lists
R1# show access-list WEB-ONLY
R1# show running-config | include access-list
R1# show ip interface Gi0/0 | include access list
| Command | Tells you |
|---|---|
show access-lists | All ACLs + their entries + match counters |
show access-list <name> | One ACL in detail |
show ip interface <int> | What ACLs are applied to that interface, in which direction |
show running-config | include access-list | All ACL config lines in flat form |
The combo of show access-lists (counters) + show ip interface (where applied + direction) covers most debug needs.
The ACL debug workflow
When an ACL “isn’t working”:
- Is it applied to the right interface?
show ip interface <int>. Check bothOutgoing access listandInbound access listlines. - Is it the right direction? Swap
in↔outand re-test if uncertain. - Are hit counters incrementing?
show access-list <name>. If counters are zero, the ACL is being bypassed — either applied wrong, or traffic isn’t on this interface. - Which line is matching? Look at counters. Sometimes a too-general
permithigher in the list catches traffic you thought would hit a later, more specific line. - Is the implicit deny biting you? Run
show access-listand check the bottom line’s hit count. If high, you’re dropping traffic you forgot to allow. - Wildcard mask correct? Common to type subnet mask by accident. Verify by sanity-checking the binary.
- Did
logreveal anything in syslog? Addlogto a suspect line, generate traffic, checkshow logging.
Worked scenarios
Scenario 1. You want to block subnet 192.168.50.0/24 from reaching 10.0.0.0/8. Where do you place the ACL?
Answer: Extended ACL (because you’re matching destination too) close to the source — on the router interface where 192.168.50.0/24 enters. Apply in. Drops traffic before it traverses any backbone link.
Scenario 2. You write an ACL with three permit lines and apply it. All other traffic is dropped. Why?
Answer: Implicit deny at the end. The router silently adds deny ip any any after your three permits. Fix: add an explicit permit ip any any at the end if you wanted only those three blocked and everything else allowed (you should rewrite the ACL with explicit denies + a final allow).
Scenario 3. ACL line: permit tcp 10.1.1.0 0.0.0.255 any eq 80. Will this match a TCP reply from a web server to 10.1.1.5?
Answer: No. The line specifies source 10.1.1.0/24 and destination port 80. Web server reply has source port 80 and destination 10.1.1.5 — that’s the opposite direction. You’d need either:
- A second line
permit tcp any eq 80 10.1.1.0 0.0.0.255on the return-direction interface, or - The
establishedkeyword on the return-direction inbound ACL.
Scenario 4. Your ACL has these two lines, in this order:
permit tcp any host 10.2.2.10 eq 80
deny tcp 192.168.1.5 0.0.0.0 host 10.2.2.10 eq 80
Will 192.168.1.5 be able to reach the web server?
Answer: Yes. First match wins. The permit tcp any matches 192.168.1.5 first; the explicit deny never gets evaluated. Fix: reorder — put the specific deny above the general permit.
Scenario 5. You apply access-list 10 deny 10.1.1.0 0.0.0.255 to an interface as ip access-group 10 in. No permits below it. What gets through?
Answer: Nothing. The deny blocks 10.1.1.0/24, then the implicit deny ip any any blocks everything else. Always end a standard ACL with permit any if you want non-denied traffic through.
Scenario 6. You want to deny SSH (port 22) to your router from everywhere except management VLAN 192.168.99.0/24. How?
Answer: Apply an ACL to the VTY lines, not an interface:
ip access-list standard MGMT-ONLY
permit 192.168.99.0 0.0.0.255
deny any log
!
line vty 0 4
access-class MGMT-ONLY in
transport input ssh
access-class on line vty is the management-plane ACL. Different from ip access-group (data-plane).
Scenario 7. ACL says:
permit tcp 10.0.0.0 0.0.0.255 any eq 80
A host at 10.0.0.5 tries to reach a web server on port 8080. Allowed?
Answer: No. eq 80 matches destination port 80 exactly. Port 8080 doesn’t match. The packet falls through to the implicit deny.
Scenario 8. You want to allow ICMP echo (ping) from a /27 subnet to a specific server, and block all other traffic from that subnet. Write the ACL.
Answer:
ip access-list extended PING-ONLY
permit icmp 10.1.1.0 0.0.0.31 host 10.2.2.10 echo
deny ip any any log
Apply in on the source-side interface. Note 0.0.0.31 = /27 wildcard.
Scenario 9. You have a wildcard mask of 0.0.0.252. What does it match?
Answer: First 30 bits exact, last 2 bits “don’t care.” If applied to a host 10.1.1.0 it matches 10.1.1.0 and 10.1.1.1 and 10.1.1.2 and 10.1.1.3 (last 2 bits varying). Discontinuous wildcards are tested on CCNA — recognize them, don’t deploy them.
Common mistakes
-
Forgetting the implicit deny. Three permits don’t help if you also forgot to allow something. Always test what you didn’t intend to block.
-
Wrong direction (in vs out). Number-one cause of “ACL isn’t working.” If unsure, swap and re-test.
-
Wildcard mask backwards. Typing
255.255.255.0where you needed0.0.0.255. Easy mistake; common exam trap. -
Too-general line above a too-specific one. First match wins. Put
host 10.1.1.5rules above subnet rules aboveanyrules. -
Applying to the wrong interface. Extended ACL belongs close to source. Standard close to destination. Mixed up = wasted bandwidth or unintended blocks.
-
Blocking return traffic. Permitting outbound TCP but blocking inbound breaks every reply. Use
establishedkeyword or move to stateful filtering. -
Editing a numbered ACL line-by-line. Impossible. Remove and re-enter, or use a named ACL.
-
Forgetting
access-classfor VTY. Usingip access-groupon the management interface doesn’t protect the VTY lines. Always pair: data-plane filter on interface, management-plane filter on VTY. -
Logging too aggressively. Adding
logto every line on a high-traffic ACL spikes CPU. Use sparingly, on specific deny lines. -
Resequencing without checking dependencies.
ip access-list resequencere-numbers lines — useful for tidying — but verify automation tools and runbooks don’t reference specific line numbers. -
Mixing IPv4 and IPv6 ACLs. They’re separate.
ip access-listis IPv4;ipv6 access-listis IPv6. Both must be applied separately to interfaces. -
Using a standard ACL where you needed extended. “Block 10.1.1.5 from reaching 10.2.2.10” is impossible with standard (which only filters source). You need extended.
Lab to try tonight
-
Topology — two LANs and one router between them. LAN-A = 10.1.1.0/24. LAN-B has a web server at 10.2.2.10 and an FTP server at 10.2.2.20.
-
Web-only goal — from LAN-A, allow only HTTP (port 80) to the web server. Block everything else. Write an extended named ACL, apply
inon the LAN-A interface, test:curl http://10.2.2.10should succeedping 10.2.2.10should failcurl ftp://10.2.2.20should failshow access-list— verify hit counters
-
Add ICMP for diagnostics — add
permit icmp 10.1.1.0 0.0.0.255 host 10.2.2.10 echoabove the deny. Ping now works. Trace flow with counters. -
Direction swap drill — move the same ACL to
outon the LAN-B interface. Observe the same end result, butshow access-listhit counters move differently (and reverse-direction traffic hits different lines). -
VTY restriction — set up
line vty 0 4withaccess-class MGMT-ONLY inallowing only one mgmt subnet. Try SSH from an unauthorized subnet — should be denied. From the mgmt subnet — should succeed. -
established trick — implement a “stateless firewall” using only ACLs. Outbound TCP from LAN-A permitted. Inbound on WAN only allowed if
established. Verify a browser session works but unsolicited inbound (e.g., remote SSH attempt to a LAN-A host) is dropped. -
Wildcard practice — use
0.0.0.7(/29) wildcard to permit only10.1.1.0–10.1.1.7. Verify hosts.5and.6are allowed;.10is denied. -
Resequence — after several inserts, run
ip access-list resequence WEB-ONLY 10 10to clean up line numbering. Verify withshow access-list. -
Bonus: time-based — define a
BUSINESS-HOURStime-range and apply to one of your rules. Verify behavior changes at the boundary.
Cheat strip
| Concept | Plain English |
|---|---|
| First match wins | Router reads top to bottom, stops on first match |
| Implicit deny | Invisible deny ip any any at the end of every ACL |
| Wildcard mask | Inverse of subnet mask. 0 = must match, 1 = don’t care |
| Standard ACL | Source IP only. 1–99 / 1300–1999. Close to destination. |
| Extended ACL | Source + dest + protocol + port + more. 100–199 / 2000–2699. Close to source. |
| Named ACL | Editable per line. Always prefer over numbered in production. |
in vs out | Direction the ACL filters relative to the interface |
host X | Shortcut for X 0.0.0.0 (one specific IP) |
any | Shortcut for 0.0.0.0 255.255.255.255 (anywhere) |
eq <port> | Destination/source port equals X. Other operators: gt, lt, range |
established | Match TCP packets with ACK/RST set (cheap stateless filter) |
log / log-input | Syslog the match. Use sparingly — CPU heavy |
access-class (VTY) | Apply ACL to management plane, not data plane |
| Resequence | ip access-list resequence NAME 10 10 cleans up line numbers |
| IPv4 ≠ IPv6 | Separate ACL types, must be applied separately |
| Time-range | Gate lines by time of day / day of week |
| Match counters | show access-list — your debug gold |
| Direction trap | Half of all ACL bugs. If rule looks right, swap direction before changing rule. |
Looking for the longer one-page reference? See the ACL Cheat Sheet.