The Zone-Based Firewall (ZBF) is the stateful firewall that lives inside Cisco IOS XE. It is not a bolt-on appliance and it is not a simple access list. It is a full policy engine that classifies traffic, inspects it statefully, and permits or drops it based on which security zone the traffic entered from and which zone it is trying to reach. If you run a branch router that also has to act as a firewall, ZBF is how you do it without buying another box. This guide is part of the PingLabz Infrastructure Security cluster, and it is the anchor article for everything ZBF: the policy model, the config, the mental model, and the one trap that locks more engineers out of their own routers than any other.
Every command and every line of output on this page came from a real Cisco Modeling Labs (CML) topology, not from memory. The firewall is a cat8000v running IOS XE 17.18.02 (EDGE1). An outside client (ISP1, an IOL-XE router at 203.0.113.2) sits on the untrusted side. An inside host (HOST1) lives on 10.20.10.0/24, and a DMZ web server (nginx) answers on 172.20.30.80. The router has three data interfaces plus its own control plane, and by the end of this article you will see all four zones and understand exactly how traffic moves between them.
A real gotcha before you type a single command: the platform
ZBF is a licensed feature, and not every IOS XE image carries it. On an IOL-XE virtual router (the lightweight IOS-on-Linux platform many of us lab on), the very first ZBF command fails:
EDGE1(config)#zone security PLZ-INSIDE
^
% Invalid input detected at '^' marker.That is not a syntax error you can work around. The Zone-Based Firewall feature set simply is not present in the IOL-XE image. We rebuilt EDGE1 on a cat8000v (the virtual Catalyst 8000V) and hit a second wall: on a fresh cat8000v the License Level is empty, and zone security is still rejected. The feature only appears after you set the license and reload:
license boot level network-advantage
! (place this line in the day0 / startup config, then reload)After the reload the router reports License Level: network-advantage with an add-on of dna-advantage, and every ZBF command is accepted. If you have been fighting an "invalid input" error on zone security, the answer is almost never your typing. It is the platform and the license. Lab this on a cat8000v with network-advantage, not on IOL-XE.
The policy model: CPL, and how it differs from the ASA MPF
If you have configured a Cisco ASA, the ZBF building blocks will feel familiar because they use the same three-layer Modular Policy Framework shape: a class-map to classify, a policy-map to act, and a service-policy to apply. ZBF calls this Cisco Policy Language (CPL). The difference is in two words and one attachment point.
First, every ZBF construct carries the keyword type inspect. A ZBF class-map is a class-map type inspect; a ZBF policy-map is a policy-map type inspect. That keyword is what makes the firewall stateful (it tracks the connection and permits the return traffic automatically). Second, and this is the real conceptual jump, you do not apply the service-policy to an interface the way the ASA MPF does. You apply it to a zone-pair: a named, directional relationship between a source zone and a destination zone. The ASA attaches policy to interface outside; ZBF attaches policy to "traffic going from the inside zone to the outside zone". PingLabz covers the appliance side of this in the Cisco ASA cluster, so if you want the MPF-on-an-interface version for contrast, that is where it lives.
Here is the real, working configuration from the lab. Read it top to bottom: three zones, a class-map that matches general TCP/UDP/ICMP traffic, a policy that inspects that class and drops everything else, a zone-pair that binds the policy to a direction, and finally an interface joined to a zone.
zone security PLZ-INSIDE
zone security PLZ-DMZ
zone security PLZ-OUTSIDE
class-map type inspect match-any PLZ-GEN-TRAFFIC
match protocol tcp
match protocol udp
match protocol icmp
policy-map type inspect PLZ-IN-TO-OUT
class type inspect PLZ-GEN-TRAFFIC
inspect
class class-default
drop log
zone-pair security PLZ-ZP-IN-OUT source PLZ-INSIDE destination PLZ-OUTSIDE
service-policy type inspect PLZ-IN-TO-OUT
interface GigabitEthernet2
zone-member security PLZ-INSIDENotice the class class-default with drop log. In a ZBF policy-map, class-default is the implicit catch-all, and if you do not add it the firewall still drops unmatched traffic. Making it explicit with log just gives you the syslog record of what got dropped. An interface with no zone-member statement is not in any zone, and traffic between a zoned interface and an unzoned interface is dropped outright.
The ZBF mental model, in three rules
Before you look at any output, internalize the three rules that govern every ZBF decision. Everything else is detail.
The design consequence is that ZBF is default-deny between zones. You build a firewall by deciding which directional flows to permit, not which to block. That is the opposite instinct from writing a deny ACL, and it is why ZBF policies read as a short list of "here is what is allowed" rather than a long list of "here is what is forbidden".
Why a zone-pair instead of an interface ACL
You could ask why any of this is better than hanging an access list on an interface, the way engineers have firewalled routers for decades. Two reasons. The first is state. A traditional ACL is a static packet filter: to allow return traffic you either open the reply ports permanently (widening your attack surface) or maintain reflexive ACLs by hand. A ZBF inspect policy tracks each connection and opens the return path only for the life of that flow, then closes it. You saw that above: the inside pinged out and the reply came back with zero inbound rules, because inspection handled the return automatically.
The second reason is that a zone-pair expresses intent at the level you actually think in. "Inside can reach the DMZ" is a policy statement about two groups of interfaces, and if you add a fourth interface to the inside zone tomorrow, it inherits every inside policy for free. An ACL is tied to one interface in one direction; a zone-pair is tied to a relationship. That is why ZBF scales to a branch router with several inside VLANs, a DMZ, and an uplink without turning into a wall of per-interface ACLs that nobody dares touch.
Seeing the zones: show zone security
With the interfaces assigned, show zone security lists every zone the router knows about. The three we created are there, and so are two you never configured:
EDGE1#show zone security
zone self 65535
Description: System defined zone
zone service 65534
Description: System defined zone
zone PLZ-INSIDE 1
Member Interfaces:
GigabitEthernet2
zone PLZ-DMZ 2
Member Interfaces:
GigabitEthernet3
zone PLZ-OUTSIDE 3
Member Interfaces:
GigabitEthernet1The two system zones matter. self (zone ID 65535) is the router itself: its control and management plane, every packet sourced by or destined to the box, including SSH sessions to it, ICMP pings of its interfaces, and routing-protocol hellos. service (zone ID 65534) is an internal system zone. You will spend most of your ZBF life ignoring the service zone and a great deal of it thinking hard about the self zone, which we come to shortly.
The companion command shows the directional bindings you built:
EDGE1#show zone-pair security
Zone-pair name PLZ-ZP-IN-DMZ 2
Source-Zone PLZ-INSIDE Destination-Zone PLZ-DMZ
service-policy PLZ-IN-TO-DMZ
Zone-pair name PLZ-ZP-IN-OUT 1
Source-Zone PLZ-INSIDE Destination-Zone PLZ-OUTSIDE
service-policy PLZ-IN-TO-OUTTwo zone-pairs, both sourced from PLZ-INSIDE: one to the outside, one to the DMZ. There is deliberately no zone-pair from outside to inside, which is exactly why the internet cannot start a connection to HOST1. The inside can reach out; the outside cannot reach in. That asymmetry is the entire point of a stateful firewall, and here it is expressed as "which zone-pairs exist".
Proof it inspects: real traffic through the firewall
A policy model is only worth anything if the packets actually flow. From HOST1 on the inside, traffic to the outside and to the DMZ both succeed, and because the policy inspects statefully, the return traffic is permitted automatically without any inbound zone-pair:
HOST1$ ping -c 3 203.0.113.2 -> 3 received, 0% packet loss
HOST1$ ping -c 2 192.168.99.1 -> 2 received, 0% packet loss
HOST1$ curl http://172.20.30.80/ -> DMZ HTTP 200The first ping reaches the outside client, the second reaches all the way to the real upstream gateway beyond it (the traffic is being inspected and forwarded through the outside zone), and the curl reaches the DMZ web server and gets an HTTP 200 back. Inside-to-outside and inside-to-DMZ both work because both directions have a zone-pair with an inspect policy. Nothing about the return path needed its own rule.
You can watch the policy itself doing the work with show policy-map type inspect zone-pair, which prints the runtime view of every zone-pair, its class-maps, and the action:
EDGE1#show policy-map type inspect zone-pair
Zone-pair: PLZ-ZP-IN-DMZ
Class-map: PLZ-GEN-TRAFFIC (match-any)
Inspect
Class-map: class-default (match-any)
Zone-pair: PLZ-ZP-IN-OUT
Class-map: PLZ-GEN-TRAFFIC (match-any)
Inspect
Class-map: class-default (match-any)Each zone-pair shows the same shape: the named class is inspected, and class-default (the implicit catch-all) sits below it to drop the rest. This is your primary ZBF troubleshooting command. If traffic is not passing, this output tells you whether the class is matching and what action is bound to it.
The self zone: the lesson everyone learns the hard way
Here is the single most important thing in this article, and the reason to read the self zone section before you ever touch a production router. By default, traffic to and from the self zone is permitted, even after you have carefully zoned every data interface. The router will still answer SSH, still reply to pings on its interfaces, and still form routing adjacencies, because you have not written any policy that touches the self zone. Proof, from the outside client pinging the router's outside interface:
ISP1#ping 203.0.113.1 repeat 3
!!!
Success rate is 100 percent (3/3)One hundred percent. The outside can ping the firewall itself, even though the outside cannot reach a single host behind it. That is the default self-zone behavior: open, because no self zone-pair exists yet.
Now the trap. The moment you create any zone-pair that involves the self zone, that direction flips from default-permit to default-deny, and you must then explicitly permit everything you still want. It is all-or-nothing per direction. To lock management down to SSH only, we added an outside-to-self policy that inspects SSH and drops the rest:
class-map type inspect match-any PLZ-MGMT-ONLY
match protocol ssh
policy-map type inspect PLZ-OUT-TO-SELF
class type inspect PLZ-MGMT-ONLY
inspect
class class-default
drop
zone-pair security PLZ-ZP-OUT-SELF source PLZ-OUTSIDE destination self
service-policy type inspect PLZ-OUT-TO-SELFThat looks reasonable: "let SSH in to the box, drop everything else." But watch what it does to the ping that was working seconds ago:
ISP1#ping 203.0.113.1 repeat 3
...
Success rate is 0 percent (0/3)From 100 percent to 0 percent, instantly, without touching ICMP at all. By creating an outside-to-self zone-pair for SSH, you switched the whole outside-to-self direction to default-deny, and ICMP is not in your permit list, so ICMP is now dropped. If your management plan had relied on ping to confirm the box was alive, on a routing protocol peering across that interface, or on anything other than the one protocol you named, all of it just went dark. This is how engineers lock themselves out with ZBF: they add a self zone-pair to allow SSH from a management network, apply it, and discover they have silently denied their own return traffic, their monitoring, and sometimes the very SSH session they are typing in.
The fix is discipline, not luck. When you write a self zone-pair, enumerate every protocol you need to reach the box (SSH, and probably ICMP, and any routing protocol or management service that terminates on the router) in that policy, because the instant the zone-pair exists, anything you did not name is denied. Test it from a second path, never from the session you are about to cut. The self zone is the most powerful and the most dangerous part of ZBF precisely because it defends the router's own brain.
Key takeaways
- ZBF is a licensed IOS XE feature, not a universal one. It is absent on IOL-XE (
zone securityreturns invalid input) and dormant on a fresh cat8000v until you setlicense boot level network-advantageand reload. - The policy model is CPL: class-map, policy-map, service-policy, all with
type inspect. Same shape as the ASA MPF, but applied to a directional zone-pair instead of an interface. - ZBF is default-deny between zones. Traffic between zones is dropped unless a zone-pair permits it; traffic within a zone is allowed; traffic to a memberless zone is a black hole.
- Two system zones always exist:
self(65535, the router's own control plane) andservice(65534).show zone securityandshow zone-pair securityare how you read the current state. - The self zone starts open and snaps shut. Self traffic is permitted until you create any self zone-pair, at which point that direction becomes default-deny and every protocol you forgot to permit (ICMP, routing, your own ping) is dropped. This is the number-one ZBF lockout.
ZBF is the router-integrated firewall in the PingLabz Infrastructure Security cluster. From here, the ZBF deep-dives get more specific: port-maps for inspecting applications on non-standard ports, nested class-maps for layered policy, and a head-to-head on where ZBF, the ASA, and FTD each belong. All of them are built on the same live cat8000v you just watched inspect real traffic.