How Network ACLs Work: Packet Filtering on Routers, Linux, and Cloud
Network Access Control Lists (ACLs) are ordered sets of rules that filter network traffic based on packet header fields -- source and destination IP addresses, protocol numbers, port numbers, and sometimes TCP flags. ACLs are the oldest and most universal traffic-filtering mechanism in networking. Every router, switch, firewall, and cloud platform implements some form of ACL, and they remain the primary tool for enforcing network security policy at the network edge, at segment boundaries, and within cloud virtual networks. Despite decades of evolution in network security, ACLs are still where policy meets packets.
The concept is deceptively simple: define a list of rules, each specifying a match condition and an action (permit or deny). The device evaluates each packet against the list, top to bottom, and applies the action from the first rule that matches. If no rule matches, an implicit deny at the end of the list drops the packet. This "first match wins" behavior, combined with the implicit deny, defines the security model of every ACL implementation -- from a Cisco router's access-list to an AWS VPC Network ACL.
But the apparent simplicity hides real complexity. Rule ordering errors are among the most common misconfigurations in production networks. The distinction between stateful and stateless filtering determines whether return traffic is automatically permitted or must be explicitly allowed. The interaction between ACLs applied at different points (interface in vs. out, router vs. host, cloud NACL vs. security group) creates layered enforcement that can be difficult to reason about. This article covers ACL mechanics across routers, Linux hosts, and cloud platforms, from the foundational concepts through advanced techniques and performance considerations.
Standard vs. Extended ACLs
The distinction between standard and extended ACLs originates from Cisco IOS but reflects a universal concept: how many header fields can a rule match on?
Standard ACLs
A standard ACL filters traffic based solely on the source IP address. It cannot match on destination address, protocol, port number, or any other field. Standard ACLs are numbered 1-99 and 1300-1999 in Cisco IOS:
# Standard ACL: permit traffic from 10.0.1.0/24, deny everything else
access-list 10 permit 10.0.1.0 0.0.0.255
# Implicit deny all at the end
The 0.0.0.255 is a wildcard mask -- the inverse of a subnet mask. Where a subnet mask has 1-bits for the network portion, a wildcard mask has 0-bits for the bits that must match and 1-bits for "don't care" bits. So 0.0.0.255 means "match the first three octets exactly, ignore the last octet" -- equivalent to /24 in CIDR notation.
Because standard ACLs can only match on source address, they should be placed as close to the destination as possible. If you place a standard ACL near the source, you might inadvertently block that source from reaching other legitimate destinations that happen to share the same path.
Extended ACLs
An extended ACL can match on source and destination addresses, protocol, source and destination ports, TCP flags, and other header fields. Extended ACLs are numbered 100-199 and 2000-2699:
# Extended ACL: allow HTTP/HTTPS from any source to web server subnet
access-list 100 permit tcp any 192.168.10.0 0.0.0.255 eq 443
access-list 100 permit tcp any 192.168.10.0 0.0.0.255 eq 80
# Allow established TCP connections (return traffic)
access-list 100 permit tcp any any established
# Allow ICMP echo (ping)
access-list 100 permit icmp any any echo
access-list 100 permit icmp any any echo-reply
# Deny and log everything else
access-list 100 deny ip any any log
The established keyword matches TCP packets with the ACK or RST flag set -- meaning the packet belongs to an already-established connection rather than a new connection initiation (SYN). This is a primitive form of stateful inspection: it allows return traffic for outbound connections without permitting new inbound connections. However, it is not truly stateful -- it does not track connection state, and it only works for TCP (not UDP or ICMP).
Extended ACLs should be placed as close to the source as possible to prevent unwanted traffic from consuming bandwidth across the network before being dropped.
Named ACLs
Both standard and extended ACLs can be created with descriptive names instead of numbers, improving readability and maintainability:
ip access-list extended WEB-SERVER-INBOUND
permit tcp any host 192.168.10.5 eq 443
permit tcp any host 192.168.10.5 eq 80
deny ip any any log
ip access-list extended MANAGEMENT-ACCESS
permit tcp 10.0.1.0 0.0.0.255 any eq 22
permit tcp 10.0.1.0 0.0.0.255 any eq 161
deny ip any any log
Named ACLs also allow inserting and deleting individual rules by sequence number, which numbered ACLs historically did not support. This eliminates the need to remove and recreate an entire ACL just to add a rule in the middle -- a significant operational improvement.
ACL Processing Order: First Match Wins
The most critical aspect of ACL behavior is the first-match-wins evaluation order. When a packet enters an interface with an applied ACL, the device tests the packet against each rule (called an Access Control Entry, or ACE) in sequence. The moment a match is found, the specified action is applied and no further rules are checked. If the packet reaches the end of the list without matching any rule, the implicit deny drops it.
This ordering behavior has critical implications for rule design:
- More specific rules must come before more general rules. A "permit ip any any" rule at line 10 renders every subsequent deny rule dead code. Similarly, a "deny ip any any" early in the list blocks traffic that a later permit rule was meant to allow.
- The implicit deny is both a safety net and a trap. It ensures that anything not explicitly permitted is blocked (a secure default), but it also means that forgetting to add a permit rule for legitimate traffic silently drops it. There are no error messages -- the traffic simply vanishes.
- Rule ordering affects performance. On a high-throughput router, a packet that matches the last rule in a 500-line ACL requires 500 comparisons. Placing the most frequently matched rules at the top of the list reduces average processing time.
A common and dangerous misconfiguration illustrates why order matters:
# WRONG: general permit before specific deny -- deny is dead code
access-list 101 permit ip any 192.168.10.0 0.0.0.255
access-list 101 deny tcp any host 192.168.10.5 eq 22 # Never reached!
# CORRECT: specific deny before general permit
access-list 102 deny tcp any host 192.168.10.5 eq 22
access-list 102 permit ip any 192.168.10.0 0.0.0.255
The Implicit Deny
Every ACL implementation ends with an invisible rule: deny all. In Cisco IOS, this is deny ip any any appended to every access-list. In JunOS, firewall filters have an implicit discard at the end of the last term. In iptables, it is the chain's default policy (typically DROP when configured for security). In AWS NACLs, it is rule number * with action DENY.
The implicit deny means that an empty ACL -- one with no rules -- blocks all traffic. This is intentional: ACLs follow a default-deny security posture. You must explicitly enumerate what is allowed. This is the opposite of many application-level authorization systems that default to open access.
The implicit deny creates a practical requirement: if you apply an ACL to a router interface, you must explicitly permit all traffic types you want to allow, including return traffic (on stateless platforms), routing protocol packets, management access, and ICMP. Forgetting any of these can cause subtle failures -- a misconfigured ACL that blocks OSPF hello packets will cause routing adjacencies to drop, potentially taking down the entire network segment.
Best practice is to add an explicit deny ip any any log as the last rule. This makes the deny visible in the configuration, and the log keyword generates a message for each dropped packet, providing visibility into what the ACL is blocking. Without the explicit deny-and-log, dropped packets vanish silently.
ACLs on Routers
Cisco IOS ACLs
In Cisco IOS, ACLs are applied to router interfaces in a specific direction: inbound (filtering packets arriving on the interface) or outbound (filtering packets leaving the interface). An interface can have one ACL applied in each direction per protocol (one for IPv4, one for IPv6).
interface GigabitEthernet0/0
description WAN-facing interface
ip address 203.0.113.1 255.255.255.0
ip access-group WAN-INBOUND in
ip access-group WAN-OUTBOUND out
interface GigabitEthernet0/1
description Internal LAN
ip address 10.0.1.1 255.255.255.0
ip access-group LAN-INBOUND in
The direction matters significantly. An inbound ACL filters packets before the routing decision -- if a packet is denied, it is dropped immediately without consuming routing table lookup resources. An outbound ACL filters packets after the routing decision, just before they are placed on the output interface's transmit queue. Inbound ACLs are generally preferred because they drop unwanted traffic earlier in the processing pipeline.
Cisco IOS also supports several specialized ACL types:
- Time-based ACLs -- rules that are active only during specified time ranges (e.g., permit internet access only during business hours)
- Reflexive ACLs -- dynamically created temporary entries that permit return traffic for outbound sessions, providing basic stateful behavior (covered in detail below)
- Dynamic ACLs (Lock-and-Key) -- rules activated only after a user authenticates via Telnet/SSH to the router, providing per-user access control
- VACL (VLAN ACLs) -- ACLs applied to VLAN traffic at the switch level, filtering traffic within a VLAN (not just between VLANs)
- Infrastructure ACLs (iACLs) -- ACLs protecting the router's own control plane, permitting only BGP (TCP 179), SSH, SNMP, and ICMP from authorized management networks
JunOS Firewall Filters
Juniper's JunOS uses the term firewall filter rather than ACL, but the concept is identical. JunOS filters use a more structured, hierarchical syntax with named terms (equivalent to ACEs):
firewall {
family inet {
filter PROTECT-RE {
term ALLOW-BGP {
from {
source-address {
198.51.100.1/32;
203.0.113.5/32;
}
protocol tcp;
destination-port bgp;
}
then accept;
}
term ALLOW-SSH {
from {
source-address {
10.0.1.0/24;
}
protocol tcp;
destination-port ssh;
}
then accept;
}
term ALLOW-ICMP {
from {
protocol icmp;
icmp-type [ echo-request echo-reply unreachable time-exceeded ];
}
then {
policer ICMP-RATE-LIMIT;
accept;
}
}
term DENY-ALL {
then {
count DENIED-PACKETS;
log;
discard;
}
}
}
}
}
interfaces {
lo0 {
unit 0 {
family inet {
filter {
input PROTECT-RE;
}
}
}
}
}
Several JunOS-specific features distinguish firewall filters from Cisco ACLs:
- Policers -- rate-limiting can be applied directly within a filter term (e.g., limit ICMP to 1 Mbps). This combines filtering and rate-limiting in one construct.
- Counters -- each term can have a named counter, providing per-rule hit statistics without the overhead of logging every packet.
- Prefix lists -- referenced within filter terms, prefix lists can be shared across multiple filters and updated independently. When a prefix list changes, all filters referencing it are automatically updated.
- Filter chains -- multiple filters can be applied to a single interface using
input-list, and they are evaluated in order. This enables modular filter design where a "base" filter handles common rules and additional filters add context-specific rules. - Commit-confirmed -- JunOS's configuration model allows you to test a new filter with automatic rollback. If the filter locks you out, the router reverts to the previous configuration after a timeout.
ACLs on Linux: iptables and nftables
Linux implements packet filtering through the Netfilter framework in the kernel. The userspace tools for configuring Netfilter rules -- iptables and its modern replacement nftables -- function as host-based ACLs, but with significantly more flexibility than router ACLs.
iptables
iptables organizes rules into tables and chains. The filter table (default) contains three built-in chains:
- INPUT -- packets destined for the local host
- OUTPUT -- packets originating from the local host
- FORWARD -- packets passing through the host (when acting as a router)
A typical server ACL in iptables:
# Default policy: drop all incoming, accept all outgoing
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow established connections (stateful -- conntrack module)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow SSH from management network only
iptables -A INPUT -p tcp --dport 22 -s 10.0.1.0/24 -j ACCEPT
# Allow HTTP/HTTPS from anywhere
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
# Allow ICMP (ping, traceroute)
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT
# Rate-limit new connections to prevent SYN floods
iptables -A INPUT -p tcp --syn -m limit --limit 60/s --limit-burst 120 -j ACCEPT
# Log dropped packets
iptables -A INPUT -j LOG --log-prefix "iptables-DROP: " --log-level 4
iptables -A INPUT -j DROP
The critical difference between iptables and router ACLs is the conntrack module. The ESTABLISHED,RELATED rule makes iptables stateful -- it tracks connection state in the kernel's connection tracking table, automatically permitting return traffic for all established connections. This single rule eliminates the need for explicit permit rules for return traffic, which is required on stateless platforms.
nftables
nftables replaced iptables as the default firewall framework in modern Linux distributions. It provides a unified syntax for IPv4, IPv6, ARP, and bridge filtering, along with first-class support for sets, maps, and atomic rule updates:
table inet filter {
set mgmt_nets {
type ipv4_addr
flags interval
elements = { 10.0.1.0/24, 172.16.0.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
# Connection tracking -- stateful filtering
ct state established,related accept
ct state invalid drop
# Loopback
iif lo accept
# SSH from management networks only
tcp dport 22 ip saddr @mgmt_nets accept
# Web traffic
tcp dport { 80, 443 } accept
# ICMP
icmp type { echo-request, echo-reply } accept
icmpv6 type { echo-request, echo-reply, nd-neighbor-solicit,
nd-neighbor-advert, nd-router-solicit,
nd-router-advert } accept
# Log and drop
log prefix "nft-DROP: " drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
nftables improves on iptables in several ways relevant to ACL management: sets allow grouping IP addresses or ports into named collections that are matched in O(1) time using hash tables (compared to O(n) sequential rule evaluation in iptables); maps allow mapping match conditions to actions or other values (e.g., redirect port 80 to service A and port 443 to service B in a single rule); and atomic updates allow replacing an entire ruleset in a single kernel transaction, eliminating the window of inconsistency that exists when iptables rules are added one at a time.
Cloud NACLs: AWS VPC NACLs vs. Security Groups
Cloud networking introduces two distinct ACL layers with fundamentally different behavior. AWS is the clearest example, but GCP and Azure have equivalent constructs.
AWS VPC Network ACLs
A VPC Network ACL (NACL) is a stateless packet filter applied at the subnet boundary. Every VPC comes with a default NACL that permits all inbound and outbound traffic. Custom NACLs start with all traffic denied (except the default allow-all rules that ship with the default NACL).
Key characteristics:
- Stateless -- NACLs do not track connection state. If you permit inbound traffic on port 443, you must also explicitly permit outbound traffic on the ephemeral port range (1024-65535) for the return traffic. Forgetting this is the single most common NACL misconfiguration.
- Rule numbers determine order -- rules are evaluated in ascending order by rule number. Rule 100 is evaluated before rule 200. The lowest-numbered matching rule determines the action.
- Separate inbound and outbound rules -- each NACL has independent rule lists for inbound and outbound traffic.
- Subnet-level association -- a NACL is associated with one or more subnets. All traffic entering or leaving the subnet passes through the NACL. Every subnet must have exactly one NACL.
- Rule * (asterisk) -- the final implicit deny-all rule, which cannot be modified or deleted.
A typical AWS NACL configuration for a web-facing subnet:
# Inbound rules
Rule 100 ALLOW TCP 0.0.0.0/0 port 443 # HTTPS
Rule 110 ALLOW TCP 0.0.0.0/0 port 80 # HTTP
Rule 120 ALLOW TCP 10.0.1.0/24 port 22 # SSH from mgmt subnet
Rule 130 ALLOW TCP 0.0.0.0/0 ports 1024-65535 # Return traffic (ephemeral)
Rule * DENY ALL 0.0.0.0/0 # Implicit deny
# Outbound rules
Rule 100 ALLOW TCP 0.0.0.0/0 ports 1024-65535 # Responses to clients
Rule 110 ALLOW TCP 0.0.0.0/0 port 443 # HTTPS to external APIs
Rule 120 ALLOW TCP 10.0.2.0/24 port 5432 # PostgreSQL to DB subnet
Rule * DENY ALL 0.0.0.0/0 # Implicit deny
Notice inbound rule 130: it permits return traffic on ephemeral ports. Without this rule, every TCP connection initiated from outside the subnet would complete the three-way handshake (the SYN and SYN-ACK would pass) but the client's data packets arriving on ephemeral ports would be dropped. This is a direct consequence of the NACL being stateless.
Security Groups
In contrast, AWS Security Groups are stateful firewalls applied at the instance (ENI) level. They track connection state, so return traffic is automatically allowed regardless of outbound rules. Security Groups differ from NACLs in several fundamental ways:
- Stateful -- if you allow inbound traffic on port 443, the response traffic is automatically permitted. No ephemeral port rules needed.
- Allow-only rules -- Security Groups only support ALLOW rules. There are no DENY rules. Anything not explicitly allowed is denied. This means you cannot create a rule like "allow all of 10.0.0.0/8 except 10.0.1.5" -- you need a NACL for explicit deny rules.
- Instance-level -- Security Groups attach to individual ENIs (Elastic Network Interfaces), not subnets. Multiple instances in the same subnet can have different Security Groups.
- Reference other Security Groups -- rules can reference other Security Groups by ID instead of IP ranges. For example, "allow TCP 5432 from sg-webapp" permits any instance in the webapp security group to connect to the database port, regardless of its IP address. This enables dynamic, instance-aware access control that adapts as instances scale up and down.
- All rules evaluated -- unlike NACLs, Security Groups evaluate all rules before making a decision. There is no ordering. If any rule permits the traffic, it is allowed.
The Two-Layer Model
In AWS, every packet traverses both the NACL and the Security Group. The NACL acts as a coarse, stateless filter at the subnet boundary, while the Security Group provides fine-grained, stateful filtering at the instance level. This two-layer model is significant:
- NACLs handle explicit denies. Since Security Groups are allow-only, you need NACLs to block specific IP ranges or protocols (e.g., blocking a known-malicious IP range, or denying all traffic from a particular country).
- Security Groups handle application-level access. Their ability to reference other security groups makes them ideal for defining service-to-service access rules (web tier to app tier, app tier to database tier).
- NACLs are the blast radius limiter. If a security group is misconfigured (e.g., accidentally opened to 0.0.0.0/0), the NACL still enforces subnet-level rules. This defense-in-depth approach prevents a single misconfiguration from exposing an entire application.
Google Cloud Platform uses VPC firewall rules (stateful, evaluated by priority number, support both allow and deny) and hierarchical firewall policies (organization/folder-level rules that override VPC-level rules). Azure uses Network Security Groups (NSGs), which are stateful and support both allow and deny rules with priority-based ordering, applied to either subnets or individual NICs.
Stateful vs. Stateless Filtering
The distinction between stateful and stateless filtering is the most important concept in understanding ACL behavior across different platforms.
Stateless Filtering
A stateless filter evaluates each packet in complete isolation. It has no memory of previous packets, no concept of "connections," and no ability to correlate a response packet with the request that triggered it. Traditional router ACLs and AWS NACLs are stateless.
The practical consequence is that you need explicit rules for both directions of every communication. If a web server on port 443 receives a client request, the response goes back to the client's ephemeral port (e.g., port 52847). On a stateless filter, you must permit outbound traffic to destination ports 1024-65535 -- otherwise the response is dropped. This is error-prone and allows any traffic on ephemeral ports, not just legitimate return traffic.
Stateless filters also cannot differentiate between a legitimate response to an outbound request and an unsolicited inbound connection attempt on the same port range. An attacker could send crafted packets to ephemeral ports that the stateless filter would permit, because those ports are opened for return traffic.
Stateful Filtering
A stateful filter maintains a connection tracking table (conntrack table) that records the state of every active connection passing through the filter. When an outbound packet creates a new connection, the filter creates a conntrack entry. When the corresponding return packet arrives, the filter matches it against the conntrack table and permits it automatically -- no explicit rule is needed.
The conntrack table tracks:
- TCP connections -- source/destination IPs, source/destination ports, TCP state (SYN_SENT, ESTABLISHED, FIN_WAIT, etc.), timeout
- UDP "connections" -- source/destination IPs and ports with a timeout (UDP is connectionless, but the filter treats a request-response pair as a logical connection)
- ICMP "connections" -- echo request/reply pairs tracked by ICMP ID
Linux's conntrack module (used by both iptables and nftables) classifies packets into states:
- NEW -- the packet starts a new connection (e.g., a TCP SYN)
- ESTABLISHED -- the packet belongs to an already-established connection
- RELATED -- the packet is related to an existing connection (e.g., an ICMP error message about an established connection, or an FTP data connection related to an FTP control connection)
- INVALID -- the packet does not match any known connection and does not appear to start a valid new connection (e.g., a TCP ACK without a preceding SYN)
Stateful filtering is more secure than stateless because it allows return traffic only for connections that were actually initiated. It is also simpler to configure -- you permit desired outbound or inbound traffic, and return traffic is handled automatically. The trade-off is resource consumption: the conntrack table requires memory (typically 256 bytes per entry), and on a high-connection-count server or firewall, the table can grow to millions of entries. The Linux default is 65536 entries (configurable via /proc/sys/net/netfilter/nf_conntrack_max), and exceeding this limit causes new connections to be dropped with "nf_conntrack: table full" errors in the kernel log.
Reflexive ACLs
Cisco IOS reflexive ACLs provide a middle ground between fully stateless extended ACLs and fully stateful firewalls. A reflexive ACL dynamically creates temporary ACL entries that permit return traffic for outbound sessions, then removes those entries when the session ends.
ip access-list extended OUTBOUND
permit tcp any any reflect TCP-SESSIONS timeout 300
permit udp any any reflect UDP-SESSIONS timeout 120
permit icmp any any reflect ICMP-SESSIONS timeout 60
ip access-list extended INBOUND
evaluate TCP-SESSIONS
evaluate UDP-SESSIONS
evaluate ICMP-SESSIONS
permit tcp any host 192.168.10.5 eq 443
deny ip any any log
interface GigabitEthernet0/0
ip access-group INBOUND in
ip access-group OUTBOUND out
When an outbound TCP connection is initiated (e.g., from 10.0.1.50:41234 to 203.0.113.10:443), the reflect keyword in the OUTBOUND ACL creates a temporary entry in the TCP-SESSIONS reflexive list: permit tcp host 203.0.113.10 eq 443 host 10.0.1.50 eq 41234. This entry is mirrored -- source and destination are swapped -- so it matches the return traffic. The evaluate statement in the INBOUND ACL checks packets against these dynamic entries before the static rules.
Reflexive ACLs provide better security than the established keyword because they create entries specific to each session (matching on all four tuple elements: source IP, source port, destination IP, destination port) rather than just checking the ACK flag. However, they are not as capable as full stateful inspection -- they cannot track TCP sequence numbers, detect out-of-state packets, or handle complex protocols like FTP that use multiple connections.
ACL Best Practices
Naming and Documentation
Always use named ACLs rather than numbered ACLs. A name like WEB-DMZ-INBOUND immediately conveys the ACL's purpose, while access-list 147 requires looking up documentation. Good naming conventions include:
- Include direction:
OUTSIDE-IN,LAN-TO-DMZ - Include purpose:
MGMT-ACCESS,ANTI-SPOOFING - Include interface:
GI0-0-INBOUND - Be consistent: pick a convention and enforce it across all devices
Every ACL should have comments (called remark in Cisco IOS) explaining why each rule exists, who requested it, and when it was added. Without this documentation, ACLs become opaque over time and no one dares modify them for fear of breaking something:
ip access-list extended WEB-INBOUND
remark --- Permit HTTPS from Internet (standard web traffic) ---
permit tcp any host 192.168.10.5 eq 443
remark --- Block known malicious range (ticket #4521, 2024-03-15) ---
deny ip 198.51.100.0 0.0.0.255 any log
remark --- Permit return traffic for outbound connections ---
evaluate RETURN-TRAFFIC
remark --- Deny and log all other traffic ---
deny ip any any log
Logging
ACL logging is essential for security monitoring, troubleshooting, and compliance auditing. However, logging has performance implications and must be used carefully:
- Always log the final deny rule -- this shows what the ACL is blocking. Without this, denied traffic vanishes silently.
- Log deny rules for specific threats -- if you block a specific IP range or protocol, log it so you can measure the volume and adjust rules.
- Be cautious logging permit rules on high-traffic flows -- logging every permitted HTTPS packet on a busy server can overwhelm the logging infrastructure. Use sampled logging or rate-limited logging for high-volume permit rules.
- Send ACL logs to a SIEM -- centralized logging enables correlation across devices. A deny on router A combined with a deny on router B might indicate a scanning attack that neither device sees in full.
- Use counters instead of logging for performance monitoring -- Cisco's
show access-listsdisplays hit counts per rule. JunOS counters provide the same information. These have negligible performance impact compared to per-packet logging.
Rule Hygiene
ACLs accumulate rules over time as new services are deployed, temporary exceptions are added, and requirements change. Without regular maintenance, ACLs become bloated and incomprehensible. Best practices include:
- Regular audits -- review ACLs at least quarterly. Remove rules with zero hit counts (after verifying they are not for infrequently used services). Remove rules tied to decommissioned services or IP ranges.
- Expiration dates on temporary rules -- if a rule is added for a time-limited purpose (a vendor needs temporary access, a migration requires a temporary opening), add a comment with an expiration date and set a calendar reminder to remove it.
- Change management -- ACL changes should go through a review process. A single misplaced rule can take down a network or create a security hole. Use config management tools (Ansible, Terraform, NAPALM) to version-control ACL configurations and enforce review workflows.
- Testing before deployment -- use tools like
Batfishto simulate ACL behavior against expected traffic patterns before deploying changes. Cisco'sshow access-listswithtest-packetallows dry-run testing against a specific packet.
Performance Impact
ACLs are evaluated for every packet that traverses the interface where they are applied. On a router forwarding millions of packets per second, the performance impact of ACL processing is a real concern.
Software vs. Hardware ACL Processing
On low-end routers and firewalls, ACL evaluation is performed in software by the CPU. Each packet is compared against rules sequentially until a match is found. A 500-rule ACL with the matching rule at position 400 requires 400 comparisons per packet. At 1 million packets per second, that is 400 million comparisons per second -- enough to saturate a CPU core.
On high-end routers and switches, ACLs are compiled into TCAM (Ternary Content-Addressable Memory). TCAM is specialized hardware that evaluates all rules simultaneously in a single clock cycle, regardless of the number of rules. A 10,000-rule ACL evaluated in TCAM takes the same time as a 10-rule ACL. This is why hardware-based forwarding platforms can apply complex ACLs at line rate (10/40/100 Gbps) without performance degradation.
However, TCAM is expensive and limited in capacity. A typical switch might have TCAM for 2,000-8,000 ACL entries. Exceeding TCAM capacity forces the remaining rules into software, dramatically reducing throughput. This is why efficient ACL design matters even on hardware platforms -- wasteful rules consume TCAM space that could be used for other functions (routing table entries, QoS policies, VLAN maps).
Optimization Strategies
- Aggregate rules using CIDR -- ten rules permitting individual /32 addresses within the same /24 can be replaced with a single rule permitting the /24. Fewer rules mean less TCAM consumption and faster software evaluation.
- Order rules by hit frequency -- in software-evaluated ACLs, place the most commonly matched rules first. If 80% of traffic matches rule 1, only 20% needs to evaluate rule 2 and beyond.
- Use object groups -- Cisco object-groups and nftables sets allow multiple values (IPs, ports) in a single rule. This is more TCAM-efficient than individual rules and more readable.
- Minimize logging on high-traffic rules -- ACL logging generates a CPU interrupt for each logged packet. On a high-speed interface, logging a frequently matched rule can push the CPU to 100% utilization. Use rate-limited logging (
log-inputwith rate limiting) or move to counter-based monitoring. - Use turbo ACLs (Cisco) -- the
access-list compiledcommand compiles ACLs into a lookup tree structure, reducing evaluation from O(n) to O(log n) for large ACLs.
Conntrack Table Sizing
For stateful ACLs (iptables/nftables), the conntrack table is the performance-critical resource. Each tracked connection consumes approximately 256-320 bytes. A server handling 500,000 concurrent connections needs at least 160 MB for the conntrack table alone. The table size and hash bucket count must be tuned for the expected connection load:
# Check current limits
cat /proc/sys/net/netfilter/nf_conntrack_max # Default: 65536
cat /proc/sys/net/netfilter/nf_conntrack_buckets # Default: 16384
# Increase for high-connection-count servers
sysctl -w net.netfilter.nf_conntrack_max=1048576
sysctl -w net.netfilter.nf_conntrack_buckets=262144
# Monitor usage
cat /proc/sys/net/netfilter/nf_conntrack_count # Current entries
When the conntrack table fills up, new connections are dropped silently (no SYN-ACK is sent). This manifests as random connection failures under load -- a common cause of production incidents on busy servers that has nothing to do with the ACL rules themselves but everything to do with the stateful tracking overhead.
ACLs vs. Firewalls
The boundary between ACLs and firewalls has blurred over decades of convergent evolution, but meaningful distinctions remain:
- Router ACLs operate at Layers 3-4, are typically stateless (except with reflexive ACLs), and are designed for high-speed packet-by-packet filtering. They are the first line of defense at the network edge and at segment boundaries. They excel at broad, coarse-grained filtering: blocking IP ranges, restricting protocols, and enforcing network topology rules.
- Stateful firewalls maintain connection state tables, track TCP sequence numbers, and can make decisions based on connection context (is this packet part of an established session?). They provide stronger security than stateless ACLs but consume more resources per connection.
- Next-generation firewalls (NGFWs) add application awareness, intrusion prevention (IPS), URL filtering, malware inspection, and user identity awareness. An NGFW can distinguish between different applications using the same port (e.g., YouTube vs. Google Docs on port 443) and apply different policies.
- Web Application Firewalls (WAFs) operate at Layer 7, parsing HTTP request structure, parameters, and payloads to detect application-specific attacks (SQL injection, XSS, CSRF). They are complementary to ACLs and network firewalls, not replacements.
In practice, production networks use all of these together in a defense-in-depth architecture. Router ACLs at the perimeter provide coarse filtering (block bogon addresses, rate-limit ICMP, restrict protocols). The stateful firewall behind the router handles connection tracking and explicit deny rules. The NGFW adds application-level inspection. The WAF protects the web application itself. Each layer catches threats that the layers above and below it cannot see.
Common ACL Use Cases
Infrastructure Protection (iACL)
Infrastructure ACLs protect the router's own control plane -- the CPU that runs BGP, OSPF, SSH, and SNMP. Without an iACL, anyone who can reach the router can attempt to establish a BGP session, brute-force SSH, or flood it with SNMP queries. An iACL applied to the loopback interface (or using CoPP -- Control Plane Policing) restricts control-plane access to authorized sources:
ip access-list extended INFRASTRUCTURE-ACL
remark --- BGP peers ---
permit tcp host 198.51.100.1 host 203.0.113.1 eq bgp
permit tcp host 198.51.100.1 eq bgp host 203.0.113.1
remark --- Management access ---
permit tcp 10.0.1.0 0.0.0.255 any eq 22
permit udp 10.0.1.0 0.0.0.255 any eq 161
remark --- ICMP for diagnostics ---
permit icmp any any echo
permit icmp any any echo-reply
permit icmp any any unreachable
permit icmp any any ttl-exceeded
remark --- Deny everything else to the router ---
deny ip any any log
Anti-Spoofing (BCP38/BCP84)
ACLs are the primary mechanism for implementing BCP38 (RFC 2827) and BCP84 (RFC 3704) ingress filtering. These RFCs require networks to drop packets with source addresses that should not originate from the interface where they arrived -- for example, packets claiming to be from 10.0.0.0/8 arriving on an internet-facing interface, or packets claiming to be from a customer's network arriving on a peering link:
ip access-list extended ANTI-SPOOF-WAN
remark --- Drop packets with bogon/private sources on WAN interface ---
deny ip 10.0.0.0 0.255.255.255 any log
deny ip 172.16.0.0 0.15.255.255 any log
deny ip 192.168.0.0 0.0.255.255 any log
deny ip 127.0.0.0 0.255.255.255 any log
deny ip 0.0.0.0 0.255.255.255 any log
deny ip 169.254.0.0 0.0.255.255 any log
deny ip 224.0.0.0 31.255.255.255 any log
remark --- Permit legitimate traffic ---
permit ip any any
Anti-spoofing ACLs are critical for preventing DDoS amplification attacks, where attackers forge source addresses to redirect response traffic at victims. If every ISP implemented BCP38, IP spoofing-based attacks would be eliminated. The RPKI framework addresses spoofing at the BGP level, but ACLs remain the enforcement mechanism at the packet level.
Traffic Classification for QoS
ACLs are not only for security. They are widely used to classify traffic for Quality of Service (QoS) policies. A QoS ACL marks packets with DSCP values based on their type, enabling differentiated handling through the network:
# Classify VoIP traffic for priority handling
access-list 150 permit udp any any range 16384 32767 # RTP media
access-list 151 permit tcp any any eq 5060 # SIP signaling
# Apply in QoS policy
class-map match-any VOICE-TRAFFIC
match access-group 150
match access-group 151
policy-map WAN-QOS
class VOICE-TRAFFIC
set dscp ef
priority 1000
ACLs in Network Automation
Manual ACL management does not scale. A network with 200 routers, each with 5-10 ACLs averaging 50 rules each, has 50,000-100,000 individual rules. Keeping these consistent, audited, and correct requires automation:
- Terraform manages cloud ACLs (AWS NACLs, Security Groups, GCP firewall rules) as infrastructure-as-code. ACL rules are defined in version-controlled HCL files, peer-reviewed in pull requests, and applied through CI/CD pipelines. This eliminates "click-ops" misconfigurations in the cloud console.
- Ansible manages router and switch ACLs using modules like
cisco.ios.ios_aclsandjunipernetworks.junos.junos_acls. ACLs are defined as YAML data structures and pushed to devices idempotently. - Batfish performs offline ACL analysis -- you feed it device configurations and ask "what traffic can flow from network A to network B?" without touching production devices. It can identify shadowed rules (rules that will never match because a broader rule above them already matches) and verify that a proposed ACL change does not break existing traffic flows.
- Capirca/Aerleon -- Google's open-source ACL generation tool takes a high-level policy definition and compiles it into vendor-specific ACL syntax (Cisco IOS, JunOS, iptables, cloud firewalls). This allows defining ACL policy once and deploying it consistently across heterogeneous network infrastructure.
See It in Action
Network ACLs guard every path between autonomous systems on the internet. The god.ad BGP Looking Glass lets you trace the networks your traffic traverses and examine the autonomous systems whose routers apply ACLs to every packet in transit:
- AS16509 -- Amazon Web Services, where VPC NACLs and Security Groups protect millions of cloud workloads
- AS15169 -- Google, which operates VPC firewall rules across its global cloud infrastructure
- AS8075 -- Microsoft Azure, home of Network Security Groups applied to subnets and NICs
- AS7018 -- AT&T, a Tier 1 ISP that applies infrastructure ACLs across thousands of router interfaces
- AS3356 -- Lumen (Level 3), one of the largest backbone operators, where BCP38 anti-spoofing ACLs filter at the network edge
Every BGP route visible in the looking glass represents a path through routers where ACLs filter traffic at each hop. Look up any IP address or autonomous system to see the AS path your packets follow -- and consider how many ACL evaluations happen for each packet along the way.