Access Control Lists

An Access Control List (ACL) is one of those Cisco technologies that looks simple until you have to troubleshoot one that is blocking something you did not expect.

At its most basic, an ACL is a list of rules that tells a router or switch what traffic to permit or deny.

You can filter traffic based on things such as:

  • Source IP address
  • Destination IP address
  • IP protocol
  • TCP or UDP ports
  • ICMP
  • Time ranges
  • Other packet characteristics supported by the platform

ACLs are commonly applied to router and Layer 3 switch interfaces, but they can also be used for other purposes such as controlling VTY access, filtering routing updates and classifying traffic for other features.

The important thing to understand is that an ACL is not a firewall.

It is a packet filter. It does not provide the stateful inspection, application awareness and broader security features you would expect from a modern firewall.

But ACLs remain extremely useful because they are built into the network infrastructure and can enforce straightforward traffic rules close to where that traffic enters or leaves the device.

Standard vs extended ACLs

For IPv4 ACLs on Cisco IOS and IOS XE, the two main types you will work with are standard and extended ACLs.

Standard ACL

A standard ACL primarily filters traffic based on the source IP address. That makes it useful when the decision you need to make is simply:

Should traffic from this source be allowed?

For example, to allow only the 192.168.10.0/24 network:

access-list 10 permit 192.168.10.0 0.0.0.255

Applied to an interface:

interface GigabitEthernet0/0
 ip access-group 10 in

This is an old-style numbered standard ACL.

The 0.0.0.255 portion is not a subnet mask. It is a wildcard mask, which is one of the most important concepts to understand when working with Cisco ACLs.

We will come back to that shortly.

Standard ACLs are generally placed close to the destination, because the ACL cannot distinguish between different destinations. Apply one too close to the source and you can block that source from reaching multiple destinations you never intended to affect.

Extended ACL

Extended ACLs give you much more control.

You can filter based on:

  • Source address
  • Destination address
  • Protocol
  • TCP or UDP ports
  • ICMP
  • Other supported packet fields

For example, this permits HTTP traffic from 192.168.10.0/24 to 10.10.20.0/24:

access-list 100 permit tcp 192.168.10.0 0.0.0.255 10.10.20.0 0.0.0.255 eq 80

Applied to an interface:

interface GigabitEthernet0/1
 ip access-group 100 in

This is much more specific because the rule considers both the source and destination, as well as the TCP port.

Extended ACLs are generally placed closer to the source, because unwanted traffic can be stopped before it travels through the rest of the network.

Named ACLs are usually easier to manage

Older Cisco configurations often use numbered ACLs such as 10 or 101.

For new configurations, I generally prefer named ACLs.

Instead of remembering what ACL 101 or ACL 150 does, give it a meaningful name:

ip access-list extended BLOCK_UNWANTED_TRAFFIC

Then add entries:

10 deny tcp any any eq 23
20 deny tcp any any eq 21
30 permit ip any any

The result is much easier to read:

Extended IP access list BLOCK_UNWANTED_TRAFFIC
    10 deny tcp any any eq telnet
    20 deny tcp any any eq ftp
    30 permit ip any any

Named ACLs also make it easier to manage individual entries using sequence numbers.

There is an important distinction here, because the terminology around numbered ACLs causes genuine confusion.

Old-style numbered ACLs vs numeric ACL names

An old-style numbered ACL is created with the traditional access-list command:

access-list 101 permit tcp any any eq 443
access-list 101 permit tcp any any eq 80

Cisco’s IP Access List Entry Sequence Numbering documentation states plainly that the sequence-numbering feature does not support old-style numbered access lists.

But it also adds something people miss:

You can name an access list with a number, so numbers are allowed when they are entered in the standard or extended named access list configuration mode.

So this is a named ACL, despite the name being 101:

ip access-list extended 101
 10 permit tcp any any eq 443
 20 permit tcp any any eq 80

It supports sequence numbers. The old-style version above does not.

That distinction matters when working on an existing network. Seeing 101 in a configuration does not by itself tell you which kind you are looking at. The syntax used to create it does.

For new configurations, the main advantages of a descriptive name are readability and easier administration. USERS_OUT tells you considerably more than 101.

Cisco also notes that sequence numbering does not support dynamic, reflexive or firewall access lists.

Wildcard masks explained

Wildcard masks are probably the part of Cisco ACL syntax that causes the most confusion when you first start configuring them.

A subnet mask and a wildcard mask do opposite things.

With a wildcard mask:

  • 0 means check this bit
  • 1 means ignore this bit

For example:

192.168.10.0 0.0.0.255

means: match the first three octets exactly, but ignore the last octet.

So it matches:

192.168.10.1
192.168.10.50
192.168.10.254

It does not match:

192.168.11.1

The relationship between a subnet mask and its wildcard mask:

Subnet mask:   255.255.255.0
Wildcard mask:   0.0.0.255

For a single host:

192.168.10.25 0.0.0.0

Or more simply:

host 192.168.10.25

Cisco also provides any as shorthand for:

0.0.0.0 255.255.255.255

If you are unsure about a wildcard mask, write down the subnet mask first and invert it. That habit avoids a lot of ACL mistakes.

ACL direction matters

An ACL applied to an interface can be inbound or outbound, and the direction is from the perspective of the interface.

interface GigabitEthernet0/0
 ip access-group USERS_IN in

in means packets are filtered as they enter that interface.

interface GigabitEthernet0/0
 ip access-group USERS_OUT out

out filters packets as they leave.

This sounds obvious, but it is one of the easiest things to get wrong when troubleshooting.

Draw the traffic path before applying the ACL:

Client
   |
   | enters Gi0/0
   v
Router
   |
   | leaves Gi0/1
   v
Server

An inbound ACL on Gi0/0 sees the traffic as it arrives from the client. An outbound ACL on Gi0/1 sees the same traffic as it leaves towards the server.

Cisco’s description of the two is worth knowing precisely. For inbound lists, permit means continue processing the packet after receiving it on the inbound interface, and deny means discard it. For outbound lists, incoming packets are routed to the outbound interface first and then processed through the outbound ACL, so permit means send it to the output buffer.

That ordering explains why an inbound ACL is more efficient. The packet is discarded before the router does the work of routing it.

Locally generated traffic

By default, an outgoing ACL on a routed interface filters transit traffic leaving the device, not traffic generated by the device itself.

Cisco IOS XE provides ip access-list match-local-traffic globally if you specifically want outgoing IPv4 ACLs to also filter locally generated traffic.

Don’t assume an outbound interface ACL will filter pings, SSH sessions or syslog messages originated by the router.

How ACL processing works

Rules are processed top to bottom

Cisco evaluates ACL entries in order and the first matching statement wins.

10 deny tcp any any eq 23
20 permit ip any any

Telnet matches sequence 10 and is denied. Everything else reaches sequence 20 and is permitted.

Reverse them:

10 permit ip any any
20 deny tcp any any eq 23

and the Telnet deny is useless, because every IP packet has already matched sequence 10.

A more specific rule must appear before a more general rule that would also match the traffic.

There is an implicit deny

At the end of a populated ACL there is an implicit deny.

Conceptually:

10 permit 192.168.10.0 0.0.0.255
20 deny everything else

You do not see that final deny in the configuration, but traffic reaching the end without matching a permit is discarded.

This catches people out when they create an ACL containing one permit statement and then discover everything else has stopped working.

An empty ACL is different

This is a Cisco-specific behaviour worth knowing exactly.

From Cisco’s helpful hints for creating IP access lists:

Create the access list before applying it to an interface. An interface with an empty access list applied to it permits all traffic.

So an empty ACL permits, while a populated ACL that has no matching permit ultimately denies.

Cisco spells out the consequence of getting this wrong:

If you applied a nonexistent access list to an interface and then proceed to configure the access list, the first statement is put into effect, and the implicit deny statement that follows could cause you immediate access problems.

That’s the real mechanism behind a lot of accidental lockouts. The ACL is applied while empty, everything works, and then the moment you type the first permit line the implicit deny springs into existence behind it and cuts off everything else.

Build and review the ACL first, then apply it.

Only one ACL per direction

On Cisco IOS and IOS XE you can have one IPv4 ACL applied per interface, per direction.

You cannot stack several IPv4 ACLs inbound on the same interface and expect them all to be evaluated independently. If you need multiple conditions, put them into the same ACL.

Editing an existing ACL

This is where sequence numbers earn their keep.

Suppose you have:

ip access-list extended USERS_OUT
 10 permit tcp 192.168.10.0 0.0.0.255 any eq 443
 20 permit tcp 192.168.10.0 0.0.0.255 any eq 80
 30 deny ip any any

You later need to permit SSH to a specific server. Insert it:

ip access-list extended USERS_OUT
 15 permit tcp 192.168.10.0 0.0.0.255 host 10.20.30.40 eq 22

The new rule lands between sequence 10 and 20.

Remove an individual entry by its sequence number:

ip access-list extended USERS_OUT
 no 20

The important point is that you delete by sequence number rather than retyping the entire permit or deny statement.

If the numbering becomes awkward, resequence:

ip access-list resequence USERS_OUT 10 10

That renumbers entries starting at 10, increasing by 10.

Before this feature existed, inserting an entry in the middle of a list meant removing everything after the desired position, adding the new entry, then re-entering everything you removed. Cisco describes that method as cumbersome and error prone, which is a fair assessment.

Verify the ACL with hit counters

Creating the ACL is only half the job. You need to confirm the traffic you think is being filtered is actually matching the rule you expect.

show ip access-lists

Or for a specific ACL:

show ip access-lists USERS_OUT

You will see the entries and, where supported, their match counters:

Extended IP access list USERS_OUT
    10 permit tcp 192.168.10.0 0.0.0.255 any eq 443 (1250 matches)
    20 permit tcp 192.168.10.0 0.0.0.255 any eq 80 (342 matches)
    30 deny ip any any (17 matches)

Those counters are the most useful troubleshooting tool you have.

If a user says HTTPS is working but your HTTPS permit has zero matches, the traffic is not hitting the rule you expected.

If the final deny is accumulating matches, traffic is reaching the ACL but not matching any preceding permit.

That gives you something concrete rather than guesswork.

To reset the counters before a test:

clear ip access-list counters USERS_OUT

Then generate the traffic and look again. A clean baseline makes it obvious which line caught it.

Be very careful when applying an ACL

There is one ACL mistake that turns a five-minute change into a trip to the comms room.

You can lock yourself out.

Suppose you’re connected over SSH through GigabitEthernet0/0 and you apply an ACL inbound that doesn’t permit your own management traffic:

interface GigabitEthernet0/0
 ip access-group MANAGEMENT_FILTER in

Your session disappears.

Before applying an ACL to the interface carrying your management session, make sure it explicitly permits the traffic you need:

ip access-list extended MANAGEMENT_FILTER
 10 permit tcp 10.10.10.0 0.0.0.255 host 192.0.2.1 eq 22
 20 deny ip any any

Do not assume that because you are currently connected, the ACL will somehow spare the existing session.

On a device you can’t reach physically, consider using reload in 10 before applying the ACL. If you lock yourself out, the router reboots to the saved configuration ten minutes later. Cancel it with reload cancel once you’ve confirmed the change worked.

What about dynamic, reflexive and time-based ACLs?

You may come across older Cisco documentation describing dynamic, reflexive and time-based ACLs. They’re worth recognising in existing configurations but don’t need to dominate a modern ACL guide.

Dynamic ACLs, sometimes called lock-and-key, dynamically permit traffic after an authentication process. Older implementations commonly used Telnet as part of that workflow. I would not introduce Telnet into a modern network to implement a legacy ACL design.

Reflexive ACLs dynamically create temporary entries based on traffic sessions. Stateful firewalls are generally a better fit for new designs.

Time-based ACLs allow an entry to be active only during a defined time range, which can still be useful for specific operational requirements.

Note that sequence numbering does not apply to dynamic or reflexive ACLs.

IPv6 ACLs are different

If your network is dual-stack, IPv6 uses separate ACL configuration and different syntax.

ipv6 access-list USERS_V6
 permit tcp 2001:db8:10::/64 any eq 443

Applied with:

interface GigabitEthernet0/0
 ipv6 traffic-filter USERS_V6 in

Note the different command: ipv6 traffic-filter rather than ip access-group.

IPv6 ACLs also use prefix length notation rather than wildcard masks, which is one fewer thing to get wrong.

If you are troubleshooting a dual-stack network, check both IPv4 and IPv6 filtering. An application can behave differently depending on which protocol it actually used.

When an ACL is not the right tool

ACLs are excellent for straightforward network-layer filtering:

Allow this subnet to reach this server on TCP 443.

They become less appropriate when you need:

  • Stateful connection tracking
  • Application identification
  • User identity
  • URL filtering
  • Malware inspection
  • Intrusion prevention
  • Centralised security management

That’s where a firewall or another dedicated control is usually more appropriate.

An ACL can still be useful alongside a firewall. The two aren’t competing technologies.

The ACL rules worth keeping in front of you

  • Create and review the ACL before applying it to a production interface
  • An empty ACL applied to an interface permits all traffic
  • Applying a nonexistent ACL and then configuring it puts the implicit deny in play from the first statement
  • Remember the implicit deny at the end of a populated ACL
  • The first matching entry wins
  • Put specific rules before broad rules
  • Check whether the ACL is applied inbound or outbound
  • Outgoing ACLs normally filter transit traffic, not traffic the router originates
  • Use named ACLs for easier management
  • Numbers are allowed as ACL names, and a numerically named ACL still supports sequence numbers
  • Use sequence numbers so entries can be added or removed cleanly
  • Check show ip access-lists and the hit counters when troubleshooting
  • Know your wildcard masks
  • Permit your own management traffic before applying an ACL to a management interface
  • On a dual-stack network, remember IPv6 uses separate configuration

The commands themselves aren’t complicated.

The difficult part is understanding which traffic the device will see, which direction it’s travelling, which rule it will hit first, and what happens to everything that doesn’t match.

Once those four things are clear, ACL troubleshooting becomes much more predictable.

Cisco references

Cisco’s documentation is platform and release specific, so check the guide for the IOS or IOS XE version you are actually configuring.

Leave a Reply

Your email address will not be published. Required fields are marked *