The router's traffic filter. Standard vs extended ACLs, the wildcard masks that trip everyone, named ACLs you can actually edit, where in the network each type belongs — and the one ACL every device needs: the lock on its own VTY lines.
An access control list is an ordered list of permit/deny rules. A packet is compared against the lines from the top; the first line that matches decides its fate and evaluation stops. If nothing matches, an invisible deny any at the bottom drops it. That's the whole engine — everything else is syntax.
ACLs are the closest thing an IOS router has to a firewall, but they're also the general-purpose "match this traffic" tool: NAT used one in the DHCP & NAT post to define who gets translated, and QoS, route-maps, and VPNs all borrow the same syntax. Learn them once for filtering and you've learned the matching language of all of IOS. We'll build standard and extended ACLs, demystify wildcard masks, edit named ACLs surgically with sequence numbers, and lock down the management plane — the ACL that protects the router itself.
01
Standard ACLs match only the SOURCE address — a blunt instrument. Extended ACLs match source, destination, protocol, and port — a scalpel. The number range tells IOS which one you mean.
| Type | Matches |
|---|---|
| Standard (1–99, 1300–1999) | Source IP only. "Traffic from these hosts." |
| Extended (100–199, 2000–2699) | Source + destination + protocol (ip/tcp/udp/icmp) + ports. "SSH from mgmt to servers." |
| Named (standard or extended) | Same engines, but with a name and editable line numbers. Use these for anything you'll keep. |
Because a standard ACL can only see the source, where you place it matters: put it near the destination, or it blocks that source from reaching everything. An extended ACL is precise, so place it near the source and kill unwanted traffic before it crosses the network. That's the classic placement rule, and it falls straight out of what each type can see.
02
ACLs don't use subnet masks. They use wildcard masks — the bitwise inverse. 0 means "must match", 1 means "don't care". Once you see it as inverse-of-the-subnet-mask, it's mechanical.
# Subnet mask 255.255.255.0 → wildcard 0.0.0.255 (a /24) 192.168.10.0 0.0.0.255 → matches 192.168.10.0 – 192.168.10.255 # One single host — every bit must match: 192.168.10.5 0.0.0.0 → exactly 192.168.10.5 (shortcut: host 192.168.10.5) # Everything — no bit has to match: 0.0.0.0 255.255.255.255 → any address (shortcut: any) # The mental shortcut for common masks: 255 minus each octet /24 → 0.0.0.255 /16 → 0.0.255.255 /30 → 0.0.0.3 /25 → 0.0.0.127 /28 → 0.0.0.15 /32 → 0.0.0.0
Writing permit 192.168.10.0 255.255.255.0 in an ACL doesn't error — IOS accepts it and matches almost nothing you intended (it reads those 255s as "don't care" octets). This is the number-one silent ACL bug. If an ACL "isn't matching anything" (show access-lists shows zero matches), check the mask first: in ACL-land it's always the wildcard, the inverse of the subnet mask. (NAT's access-list, from the previous post, uses wildcards too.)
03
The two-step ritual for every ACL: build the list in global config, then apply it to an interface with a direction. An ACL that isn't applied filters nothing.
# 1. Build the list (numbered standard, 1-99) R1(config)# access-list 10 deny 192.168.20.0 0.0.0.255 R1(config)# access-list 10 permit any # Without "permit any", the implicit deny would block EVERYONE. # 2. Apply it OUTBOUND on the interface facing the servers # (standard ACL → near the destination) R1(config)# interface GigabitEthernet0/2 R1(config-if)# ip access-group 10 out R1(config-if)# end # Verify: rules + hit counters (safe) R1# show access-lists 10 Standard IP access list 10 10 deny 192.168.20.0, wildcard bits 0.0.0.255 (43 matches) 20 permit any (1287 matches)
Direction is from the router's point of view. in = packets arriving at this interface from the wire; out = packets the router is about to send out of it. Getting this backwards is the second-most-common ACL bug after wildcard masks.
04
Protocol, source, destination, port. Extended ACLs express real policy: "guests get web and DNS, and nothing else toward the LAN."
# Named extended ACL — use names for anything permanent R1(config)# ip access-list extended GUEST-IN # Allow DNS and web to the internet, block everything toward the LAN R1(config-ext-nacl)# permit udp 192.168.20.0 0.0.0.255 any eq 53 R1(config-ext-nacl)# permit tcp 192.168.20.0 0.0.0.255 any eq 443 R1(config-ext-nacl)# permit tcp 192.168.20.0 0.0.0.255 any eq 80 R1(config-ext-nacl)# deny ip 192.168.20.0 0.0.0.255 192.168.10.0 0.0.0.255 R1(config-ext-nacl)# deny ip 192.168.20.0 0.0.0.255 192.168.99.0 0.0.0.255 R1(config-ext-nacl)# permit ip 192.168.20.0 0.0.0.255 any R1(config-ext-nacl)# exit # Apply INBOUND on the guest-facing interface (extended → near the source) R1(config)# interface GigabitEthernet0/1.20 R1(config-subif)# ip access-group GUEST-IN in R1(config-subif)# end
First match wins, so a rule's position is part of the policy. In GUEST-IN, the web/DNS permits sit above the LAN denies — swap them and guests lose DNS if your DNS server lives in the LAN range. When an ACL misbehaves, read it top-down as the packet would: show access-lists shows per-line match counters, and the line soaking up the hits is where your traffic is actually landing — often not the line you thought.
Two keywords worth knowing early. permit tcp any any established matches only TCP packets that belong to already-started conversations (ACK/RST set) — the classic "replies may come back, but outsiders can't initiate" line on an inbound WAN ACL. And appending log to a deny (deny ip any any log as your explicit last line) makes the invisible visible: you get a syslog entry for every drop, which turns "the app doesn't work" into "the ACL is dropping TCP/8443 from .20.14, add a permit." An explicit final deny with log costs nothing and replaces guessing with evidence.
05
The old pain: numbered ACLs couldn't be edited — you deleted and retyped the whole list. Named ACL config mode fixes this with sequence numbers. This is why you use named ACLs.
# The lines are numbered 10, 20, 30... — gaps are for insertions R1# show access-lists GUEST-IN Extended IP access list GUEST-IN 10 permit udp 192.168.20.0 0.0.0.255 any eq domain 20 permit tcp 192.168.20.0 0.0.0.255 any eq 443 30 permit tcp 192.168.20.0 0.0.0.255 any eq www 40 deny ip 192.168.20.0 0.0.0.255 192.168.10.0 0.0.0.255 # Insert a new rule BETWEEN 10 and 20 — give it sequence 15 R1(config)# ip access-list extended GUEST-IN R1(config-ext-nacl)# 15 permit udp 192.168.20.0 0.0.0.255 any eq 123 # NTP # Delete one line by its sequence number — the rest stay untouched R1(config-ext-nacl)# no 30 R1(config-ext-nacl)# exit # Out of gaps? Renumber: start at 10, step by 10 R1(config)# ip access-list resequence GUEST-IN 10 10
On a classic numbered ACL, typing no access-list 110 deny … intending to remove one line removes the entire ACL 110 — and an interface with ip access-group 110 in now filters nothing (an applied-but-nonexistent ACL permits everything). If that ACL was your WAN filter, you just opened the router. Edit numbered ACLs only by rewriting them offline and pasting; better, use named ACLs and sequence numbers and never face the problem.
06
The most important ACL on any device is the one that decides who may even attempt to SSH in. An interface ACL filters transit traffic; access-class on the VTY lines guards the management plane from every direction at once.
# Standard ACL is enough here — we only care about WHO (source) R1(config)# ip access-list standard MGMT-ONLY R1(config-std-nacl)# permit 192.168.99.0 0.0.0.255 R1(config-std-nacl)# deny any log R1(config-std-nacl)# exit # Apply with access-class on the VTY lines — not ip access-group R1(config)# line vty 0 4 R1(config-line)# access-class MGMT-ONLY in R1(config-line)# end # Now SSH attempts from anywhere outside 192.168.99.0/24 are refused # at the line — regardless of which interface they arrived on.
You could add "deny SSH to the router" lines on every interface ACL — and forget one, which is exactly what attackers count on. access-class sits on the VTY lines themselves, so it covers every current and future interface in one place. This pairs with the SSH setup from First Contact: transport input ssh decides how you may connect, access-class decides from where. On gear we hand over, both are non-negotiable.
07
Three questions solve most ACL tickets: what do the rules actually say, which line is the traffic hitting, and is the ACL applied where you think it is.
| Command | Answers |
|---|---|
| show access-lists | Every ACL with per-line match counters — the ground truth. |
| show access-lists GUEST-IN | One ACL's lines and counters. |
| show ip interface gi0/1 | Which ACL is applied on this interface, and in which direction. |
| show run | section access-list | The ACLs as configured, including remarks. |
| show run | section line vty | Confirms the access-class guarding management. |
| clear access-list counters | Zero the match counters before a test, so hits are unambiguous. |
# 1. Zero the counters R1# clear access-list counters GUEST-IN # 2. Reproduce the problem (have the user retry / send the traffic) # 3. Read which line moved R1# show access-lists GUEST-IN ... 40 deny ip 192.168.20.0 0.0.0.255 192.168.10.0 0.0.0.255 (17 matches) # 17 new matches on line 40 — the traffic is being eaten by the LAN deny. # Decision is now informed: insert a permit above 40, or confirm it's policy.
08
RouterOS firewall rules and IOS ACLs solve the same problem with different engines — one is stateful by default, the other is a stateless list you make smarter.
| MikroTik / RouterOS | Cisco IOS |
|---|---|
| /ip firewall filter rule | An ACL line (permit/deny) |
| chain=forward | ACL applied to a transit interface (in/out) |
| chain=input (to the router) | access-class on VTY + control-plane policies |
| connection-state=established,related | permit tcp any any established (TCP-only, cruder) |
| src-address=192.168.10.0/24 | 192.168.10.0 0.0.0.255 (wildcard, not CIDR) |
| log=yes | log keyword on the ACL line |
| Rules evaluated top-down, first match | Same — and both end in an implicit drop/deny |
RouterOS filter is a stateful firewall: connection tracking is built in, and established,related handles return traffic for every protocol. Classic IOS ACLs are stateless — each packet is judged alone, and established only approximates state for TCP. That's fine for segmentation and management-plane control, which is what ACLs are for. When a site needs a real stateful edge on Cisco, that's Zone-Based Firewall (or a dedicated firewall appliance) — a different tool, and a topic for later in this series.
Takeaways
/24 → 0.0.0.255. Typing a subnet mask by habit doesn't error — it just silently matches the wrong thing.in arriving, out leaving.15 permit …, delete with no 30, tidy with resequence. Numbered ACLs delete wholesale — no access-list 110 removes the entire list and fails open.ip access-group … in|out — and verify with show ip interface, not memory.access-class on the VTY lines restricts who may even attempt SSH, on every interface at once. It's the highest-value five lines of security on the box.clear access-list counters, reproduce, show access-lists — the line that moved is the line responsible. Add log to explicit denies and drops stop being mysteries.NOCTIS designs and audits filtering policies on Cisco and MikroTik — guest isolation, management-plane lockdown, and rules your team can read. We document every ACL line with its reason, so the policy survives staff changes.
Book a Discovery Call →