ACL & Firewall Rules
Control which traffic is allowed through your network, and in which direction.
What Is an ACL?
An Access Control List is an ordered list of permit and deny statements. A router or switch checks each packet against the list. ACLs can match on:
- Source and destination IP address (Layer 3)
- Protocol: IP, TCP, UDP, ICMP, OSPF, and so on
- Source and destination port (Layer 4)
Besides filtering traffic, ACLs also pick out traffic for NAT, QoS, route maps, VPNs, and limiting who can reach VTY (SSH) lines.
How ACLs Are Processed
- Top-down: statements are checked in order (by sequence number).
- First match wins: once a packet matches, the action is taken and no more lines are checked.
- Implicit deny: every ACL ends with an invisible
deny any. A packet that matches nothing is dropped. - One ACL per interface, per direction, per protocol (for example, one inbound IPv4 ACL on Gi0/0).
Order matters. Put the most specific rules first. A broad permit ip any any near the top makes every line below it useless.
Wildcard Masks
Cisco ACLs use wildcard masks, which work like an inverted subnet mask. A 0 bit means "must match" and a 1 bit means "don't care." A quick way to get one: subtract the subnet mask from 255.255.255.255.
| Match | Subnet mask | Wildcard | Shortcut |
|---|---|---|---|
| One host 10.1.1.5 | 255.255.255.255 | 0.0.0.0 | host 10.1.1.5 |
| 192.168.10.0/24 | 255.255.255.0 | 0.0.0.255 | — |
| 172.16.0.0/16 | 255.255.0.0 | 0.0.255.255 | — |
| 10.0.0.0/30 | 255.255.255.252 | 0.0.0.3 | — |
| Everything | 0.0.0.0 | 255.255.255.255 | any |
Standard ACLs
Standard ACLs match on the source IP only. Numbered ranges: 1–99 and 1300–1999.
Example: only 192.168.10.0/24 may reach the server LANRouter(config)# access-list 10 remark ALLOW USERS TO SERVERS
Router(config)# access-list 10 permit 192.168.10.0 0.0.0.255
! implicit deny any
Router(config)# interface gigabitEthernet0/2 ! server LAN interface
Router(config-if)# ip access-group 10 out
Example: allow SSH to the router only from the admin subnet
Router(config)# ip access-list standard MGMT-ONLY
Router(config-std-nacl)# permit 192.168.99.0 0.0.0.255
Router(config-std-nacl)# deny any log
Router(config)# line vty 0 15
Router(config-line)# access-class MGMT-ONLY in
Router(config-line)# transport input ssh
Extended ACLs
Extended ACLs match on source, destination, protocol and ports. Numbered ranges: 100–199 and 2000–2699. Named ACLs are easier to read and edit, so prefer those.
Syntax: permit|deny protocol source [port] destination [port]
Router(config)# ip access-list extended WEB-IN
Router(config-ext-nacl)# 10 permit tcp any host 203.0.113.10 eq 443
Router(config-ext-nacl)# 20 permit tcp any host 203.0.113.10 eq 80
Router(config-ext-nacl)# 30 permit tcp 198.51.100.0 0.0.0.255 host 203.0.113.10 eq 22
Router(config-ext-nacl)# 40 permit icmp any host 203.0.113.10 echo
Router(config-ext-nacl)# 50 permit tcp any any established
Router(config-ext-nacl)# 1000 deny ip any any log
Router(config)# interface gigabitEthernet0/0 ! Internet-facing
Router(config-if)# ip access-group WEB-IN in
Editing named ACLs: sequence numbers let you insert a line without retyping the whole list, for example 15 permit tcp any host 203.0.113.10 eq 8443. Remove a line with no 15.
eq 22 (ssh) eq 23 (telnet) eq 25 (smtp) eq 53 (domain)
eq 80 (www) eq 443 eq 3389 range 49152 65535
gt 1023 lt 1024 neq 23
Placement and Direction
- Inbound (
in): filters packets as they arrive on the interface, before routing. More efficient, because dropped packets never get routed. - Outbound (
out): filters packets after routing, as they leave the interface.
| ACL type | Place it | Why |
|---|---|---|
| Extended | As close to the source as possible | It's specific enough to drop unwanted traffic early without affecting other traffic. |
| Standard | As close to the destination as possible | It only knows the source, so placing it near the source would block that host from everything. |
Stateless vs Stateful Firewalls
| Stateless (router ACL) | Stateful firewall | |
|---|---|---|
| Tracks connections? | No. Each packet is judged on its own. | Yes, through a session/state table. |
| Return traffic | Needs its own rule (for example established) | Allowed automatically if the session started from the inside |
| Examples | Cisco IOS ACLs, Junos firewall filters, AWS Network ACLs | Cisco ASA/FTD, Juniper SRX, Palo Alto, Fortinet, Linux nftables/firewalld, AWS Security Groups |
Next-generation firewalls (NGFW) add Layer 7 awareness on top of state tracking: they can identify applications, inspect TLS, prevent intrusions (IPS), and write rules based on users.
Junos Firewall Filters
Junos uses firewall filters made of named terms. Each term has from (match conditions) and then (action). Like Cisco ACLs, terms are checked in order and there's an implicit discard at the end.
set firewall family inet filter PROTECT-RE term SSH from source-address 192.168.99.0/24
set firewall family inet filter PROTECT-RE term SSH from protocol tcp
set firewall family inet filter PROTECT-RE term SSH from destination-port ssh
set firewall family inet filter PROTECT-RE term SSH then accept
set firewall family inet filter PROTECT-RE term OSPF from protocol ospf
set firewall family inet filter PROTECT-RE term OSPF then accept
set firewall family inet filter PROTECT-RE term ICMP from protocol icmp
set firewall family inet filter PROTECT-RE term ICMP then policer ICMP-1M
set firewall family inet filter PROTECT-RE term ICMP then accept
set firewall family inet filter PROTECT-RE term DENY-ALL then count DENIED
set firewall family inet filter PROTECT-RE term DENY-ALL then log
set firewall family inet filter PROTECT-RE term DENY-ALL then discard
set firewall policer ICMP-1M if-exceeding bandwidth-limit 1m burst-size-limit 15k
set firewall policer ICMP-1M then discard
set interfaces lo0 unit 0 family inet filter input PROTECT-RE
Careful: a filter on lo0 affects all traffic to the router itself. Allow every protocol you use (BGP, BFD, NTP, DNS, SNMP, and so on) before you commit, and use commit confirmed 5 so the change rolls back automatically if you lock yourself out.
Linux firewalld and Cloud Firewalls
On RHEL, Rocky and Fedora servers, firewalld manages the kernel's nftables firewall. It's stateful, organized into zones, and denies inbound traffic by default.
# Allow web traffic
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Allow SSH only from the admin subnet (rich rule)
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.99.0/24" service name="ssh" accept'
# Open a custom port
sudo firewall-cmd --permanent --add-port=8443/tcp
# Apply and review
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
AWS: Security Groups vs Network ACLs
| Security Group | Network ACL | |
|---|---|---|
| Applies to | Instance / ENI | Subnet |
| State | Stateful | Stateless |
| Rules | Allow only | Allow and deny |
| Evaluation | All rules are evaluated | In number order, first match wins |
Best Practices
- Least privilege: allow only what's needed and deny everything else.
- End with an explicit
deny ip any any logso you can see what's being dropped. - Use names and remarks so the next engineer (or you, six months from now) knows why each rule exists.
- Leave gaps in sequence numbers (10, 20, 30) so you can insert rules later.
- Don't lock yourself out: use console access,
reload in 10(Cisco) orcommit confirmed(Junos) when changing remote ACLs. - Watch the hit counters:
show access-lists(Cisco) orshow firewall(Junos) to confirm rules match what you expect. - Review regularly and remove rules that are no longer needed.
Check Yourself
1. What happens to a packet that doesn't match any line in an ACL?
It's dropped by the implicitdeny any at the end.