ACLs judge every packet alone. A real firewall remembers conversations — let a reply back in because you allowed the request out. The Zone-Based Firewall turns an IOS router into exactly that: group interfaces into zones, and write stateful policy for traffic between them.
The ACL post ended honestly: classic ACLs are stateless. Each packet is judged in isolation, so to let web replies back in you either open a wide inbound hole or lean on the crude established keyword — TCP-only, and blind to the actual conversation. A real firewall is stateful: it remembers that an inside host opened a connection outward, and automatically permits that connection's return traffic while blocking everything unsolicited. No mirror rules, every protocol.
Cisco's Zone-Based Firewall (ZBF) brings stateful inspection to any IOS/IOS-XE router — no separate appliance. The model is clean once it clicks: put interfaces into security zones (LAN, WAN, DMZ), and traffic between any two zones is denied by default until you attach a zone-pair policy that says otherwise. The policy is built from the same modular CLI (class-maps and policy-maps) that QoS uses. This post builds a working three-zone edge — internet access out, nothing unsolicited in, a published DMZ server — and the model scales straight to guest isolation.
show version / license)01
Four moving parts, always in the same order: define zones, match traffic (class-map), decide what to do with it (policy-map), and bind the policy to a direction between two zones (zone-pair).
| Piece | Role |
|---|---|
| zone security | A named group of interfaces (LAN, WAN, DMZ). |
| class-map type inspect | Matches which traffic — usually via an ACL. "What are we talking about?" |
| policy-map type inspect | Says what to do with each class: inspect / pass / drop. "What do we do with it?" |
| zone-pair security | Binds a policy to traffic FROM one zone TO another. Direction matters. |
Two rules make the whole system predictable. Traffic within the same zone flows freely (two LAN interfaces talk with no policy). Traffic between different zones is dropped unless a zone-pair explicitly allows it — and a zone-pair is unidirectional, so LAN→WAN and WAN→LAN are separate policies. An interface with no zone can't talk to any interface that has one, which is a classic "why did everything stop" surprise.
02
Name the trust domains, then assign each interface to exactly one. This is the moment the firewall starts dropping inter-zone traffic, so plan before you assign.
R1(config)# zone security LAN R1(config)# zone security WAN R1(config)# zone security DMZ R1(config)# interface GigabitEthernet0/1 R1(config-if)# zone-member security LAN R1(config-if)# exit R1(config)# interface GigabitEthernet0/0 R1(config-if)# zone-member security WAN R1(config-if)# exit R1(config)# interface GigabitEthernet0/2 R1(config-if)# zone-member security DMZ R1(config-if)# end
The instant an interface joins a zone, all traffic to/from other zones — including your own SSH session if it crosses a zone boundary — is dropped until a zone-pair permits it. Assign the WAN or management interface to a zone with no matching self-zone policy and you'll lose the box. Two defences: configure over the console for the initial cutover, and remember the special self zone (next gotcha) governs traffic to the router itself. Build the zone-pairs first, assign interfaces last, or do it on the console.
03
Define what traffic you care about (an ACL wrapped in a class-map), then a policy that inspects it. "Inspect" is the magic word — it means stateful.
# What traffic? An ACL, referenced by a class-map (the ACL habit pays off) R1(config)# ip access-list extended LAN-OUT R1(config-ext-nacl)# permit ip 192.168.1.0 0.0.0.255 any R1(config-ext-nacl)# exit R1(config)# class-map type inspect match-any LAN-TRAFFIC R1(config-cmap)# match access-group name LAN-OUT R1(config-cmap)# exit # What do we do? Inspect it — this is the STATEFUL action R1(config)# policy-map type inspect LAN-TO-WAN-POLICY R1(config-pmap)# class type inspect LAN-TRAFFIC R1(config-pmap-c)# inspect R1(config-pmap-c)# exit R1(config-pmap)# class class-default R1(config-pmap-c)# drop log # everything else out this pair: drop + log R1(config-pmap-c)# end
A policy class has three possible actions. inspect is stateful: it permits the traffic and builds a session so the return traffic is auto-allowed — this is what you want for TCP/UDP/ICMP flows initiated by a trusted zone. pass permits statelessly in one direction only (no return session) — used for stateless protocols like some IPsec/ESP where the router shouldn't track state. drop denies. The class class-default at the bottom of every policy is your explicit default-deny — always set it to drop (add log to see what you're refusing), the ZBF equivalent of an ACL's implicit deny made visible.
04
Nothing happens until a zone-pair connects the policy to a direction. This is the line that actually turns traffic on between two zones.
R1(config)# zone-pair security LAN-TO-WAN source LAN destination WAN R1(config-sec-zone-pair)# service-policy type inspect LAN-TO-WAN-POLICY R1(config-sec-zone-pair)# end # That's it. LAN hosts now reach the internet, and because the sessions # are INSPECTED, all their return traffic is auto-permitted. There is NO # WAN-TO-LAN zone-pair, so unsolicited inbound is dropped by default — # exactly the stateful behaviour ACLs made you fake. R1# show policy-map type inspect zone-pair sessions Zone-pair: LAN-TO-WAN Number of established sessions = 42 Established Sessions Session 192.168.1.20:51344=>93.184.216.34:443 tcp SIS_OPEN
05
The one case where you deliberately allow unsolicited inbound: a public web server in the DMZ. A WAN→DMZ zone-pair, scoped to exactly one port, paired with the static NAT from the NAT post.
# Match ONLY tcp/443 to the DMZ server R1(config)# ip access-list extended WEB-IN R1(config-ext-nacl)# permit tcp any host 192.168.50.10 eq 443 R1(config-ext-nacl)# exit R1(config)# class-map type inspect match-any WEB R1(config-cmap)# match access-group name WEB-IN R1(config-cmap)# exit R1(config)# policy-map type inspect WAN-TO-DMZ-POLICY R1(config-pmap)# class type inspect WEB R1(config-pmap-c)# inspect R1(config-pmap)# class class-default R1(config-pmap-c)# drop log R1(config-pmap-c)# exit R1(config)# zone-pair security WAN-TO-DMZ source WAN destination DMZ R1(config-sec-zone-pair)# service-policy type inspect WAN-TO-DMZ-POLICY R1(config-sec-zone-pair)# end # Pair with: ip nat inside source static tcp 192.168.50.10 443 interface g0/0 443
On inbound WAN→DMZ traffic, NAT untranslates the destination before the firewall inspects it, so your ZBF ACL must match the server's private address (192.168.50.10), not its public one. Get this backwards and the class never matches, the traffic hits class-default, and it's silently dropped — a very common "port-forward + firewall = nothing works" cause. When in doubt, show policy-map type inspect zone-pair shows per-class counters: if WEB shows zero and class-default is climbing, your match address is wrong.
06
Traffic TO the router itself (SSH, SNMP, routing protocols, your IP SLA pings) lives in a special built-in zone called "self". By default self-traffic is allowed — tighten it deliberately.
# Match management protocols destined to the router R1(config)# ip access-list extended TO-ROUTER R1(config-ext-nacl)# permit tcp 192.168.1.0 0.0.0.255 any eq 22 R1(config-ext-nacl)# permit icmp any any R1(config-ext-nacl)# exit R1(config)# class-map type inspect match-any MGMT R1(config-cmap)# match access-group name TO-ROUTER R1(config)# policy-map type inspect TO-SELF R1(config-pmap)# class type inspect MGMT R1(config-pmap-c)# inspect R1(config-pmap)# class class-default R1(config-pmap-c)# drop R1(config-pmap-c)# exit R1(config)# zone-pair security WAN-TO-SELF source WAN destination self R1(config-sec-zone-pair)# service-policy type inspect TO-SELF R1(config-sec-zone-pair)# end # This complements the VTY access-class from the ACL post — belt and braces.
07
ZBF fails in predictable ways — an unassigned interface, a missing zone-pair, or a class that never matches. These commands localize all three.
| Command | Shows |
|---|---|
| show zone security | Every zone and its member interfaces — spot the unassigned one. |
| show zone-pair security | All zone-pairs and the policy attached — spot the missing direction. |
| show policy-map type inspect zone-pair | Per-class packet counters — the "which class is eating my traffic" view. |
| show policy-map type inspect zone-pair sessions | The live stateful session table. |
| show class-map type inspect | What each class-map matches. |
Not everything needs ZBF. For simple L3 segmentation and management-plane control — guest can't reach the LAN, only mgmt subnet may SSH — ACLs are lighter and perfectly adequate, and that's most SMB switch/router work. Reach for ZBF when you need true statefulness at an internet edge: return-traffic handling for every protocol, a DMZ, protocol inspection, or per-zone policy that would be a nightmare of mirrored ACLs. On sites that need a serious stateful edge, a dedicated firewall (or IOS-XE's newer policies) may be the better call — ZBF is the "real firewall without buying an appliance" middle ground, and it's plenty for most Cretan SMBs.
Takeaways
established.class class-default drop log as your visible default-deny.access-class.NOCTIS builds stateful Zone-Based Firewall policies on Cisco routers — internet edges, DMZs, guest isolation — for hotels and SMBs in Crete. Locked down by default, documented zone by zone, and pressure-tested from the outside.
Book a Discovery Call →