Most Zone-Based Firewall policies you will see are flat: a class-map lists a handful of match protocol lines, a policy-map inspects that class, and a zone-pair binds it to a direction. That works, but it does not scale, and it cannot express certain kinds of logic at all. The moment you want to reuse a group of protocols across several policies, or combine "match all of these conditions" with "match any of those protocols" in one class, you need class-maps that reference other class-maps. That is nesting, and it is the difference between a policy you copy-paste and one you actually maintain. This article is part of the PingLabz Infrastructure Security cluster and continues the Zone-Based Firewall pillar, so the zone and zone-pair concepts here assume you have read that first.
Every configuration and every line of show output below came from a real cat8000v running IOS XE 17.18.02 in Cisco Modeling Labs.
match-all versus match-any: the two logics
Before nesting makes sense, you have to be crisp about the two ways a class-map can evaluate its match statements, because nesting is really about combining them.
A flat class-map picks one of these and lives with it. That is the limitation. A single match-any class can say "http or https", and a single match-all class can require several conditions, but neither one can express "match ALL of these requirements, where one of the requirements is itself a choice of ANY of these protocols." To combine the two logics in one decision, one class-map has to reference another. That reference is what match class-map gives you.
Building a nested class-map
Here is the pattern from the lab. We define a reusable protocol group as a match-any child class, then reference it from a match-all parent class, and inspect the parent in the DMZ policy:
class-map type inspect match-any PLZ-WEB-PROTOCOLS
match protocol http
match protocol https
class-map type inspect match-all PLZ-NESTED-WEB
match class-map PLZ-WEB-PROTOCOLS
policy-map type inspect PLZ-IN-TO-DMZ
class type inspect PLZ-NESTED-WEB
inspectRead the structure from the bottom up. The policy-map inspects the class PLZ-NESTED-WEB. That class is match-all, and its only match statement is match class-map PLZ-WEB-PROTOCOLS: it says "match if the child class matches." The child, PLZ-WEB-PROTOCOLS, is match-any and matches HTTP or HTTPS. So the parent inherits "http OR https" as one of its conditions. Right now the parent has only that one condition, so functionally it behaves like the child, but the structure is what matters: you now have a parent into which you can add more match-all requirements alongside the reusable web-protocol group.
Proof the nesting actually evaluates
Nesting is only trustworthy if you can see the router evaluating it, and you can. show policy-map type inspect zone-pair against the DMZ zone-pair prints the parent class, its reference to the child, and the child's own matches, indented to show the hierarchy:
EDGE1#show policy-map type inspect zone-pair PLZ-ZP-IN-DMZ
Class-map: PLZ-NESTED-WEB (match-all)
Match: class-map match-any PLZ-WEB-PROTOCOLS
Match: protocol http
Match: protocol https
Inspect
Class-map: class-default (match-any)
Match: anyThat output is the whole argument for this article. The top line is the parent, PLZ-NESTED-WEB, and IOS labels it (match-all). Below it, indented, is Match: class-map match-any PLZ-WEB-PROTOCOLS: the parent's single condition is the child class, and IOS even tells you the child is match-any. Indented one level deeper are the child's own two matches, protocol http and protocol https. The Inspect action hangs off the parent. Then class-default sits below to catch everything else. You are looking at the evaluation order made explicit: parent (match-all) references child (match-any), child matches http or https, parent inspects.
This is exactly what you want when you troubleshoot a nested policy. You do not have to reason about whether the reference "took"; the runtime output shows the full tree, so you can confirm the child is being consulted and that its protocol list is what you think it is.
Growing the parent without touching the child
The version in the lab keeps the parent minimal so the hierarchy is easy to read, but the reason you build it this way is what comes next. Because the parent is match-all, you can add further requirements to it and they combine with AND logic against the web-protocol group already supplied by the child. A parent that today says "match if the traffic is a web protocol" can grow to "match if the traffic is a web protocol and originates from a specific source group," and the child class never changes. That is the structural payoff: the choice of protocols (the OR) lives in the child, the set of mandatory conditions (the AND) lives in the parent, and you extend the parent without disturbing the reusable piece underneath it.
Contrast that with a flat class-map. To add a source restriction to a flat match-any web class, you cannot simply bolt it on, because adding a match statement to a match-any class widens the match (it becomes another OR branch) rather than narrowing it. You would have to rebuild the class as match-all and lose the "http OR https" flexibility in the process. Nesting is what lets you keep both behaviors in the same policy: OR where you want choices, AND where you want requirements. The two-level structure is not decoration; it is the only way to hold both logics at once.
Why nest at all: two concrete payoffs
Nesting is more typing than a flat class-map, so it has to earn its keep. It does, in two ways that flat class-maps simply cannot match.
Reuse: define once, reference everywhere
Define PLZ-WEB-PROTOCOLS as "http and https" one time, and every policy that needs the web-protocol set references the same child class. When the definition of "web protocols" changes (someone adds a QUIC or an alternate HTTPS port to the standard), you edit one child class-map and every parent that references it inherits the change. In a flat design you would have the same match protocol http / match protocol https pair copied into the inside-to-DMZ policy, the guest-to-DMZ policy, and the partner-to-DMZ policy, and you would have to remember to update all three. Nesting turns three edits and a chance to forget one into a single authoritative definition. That is the same maintainability instinct behind port-maps: change the meaning in one place and let everything that references it follow.
Combined logic a flat class-map cannot express
The second payoff is expressiveness. A flat class-map is one logic type, match-all or match-any, and it applies that single logic to every statement. Nesting lets you compose them. The canonical example: a match-all parent that requires "is a web protocol AND comes from this source AND uses this application signature", where "is a web protocol" is itself a match-any choice of http or https, supplied by the child. There is no way to write "AND (http OR https)" in a single flat class-map, because a flat class-map cannot hold a nested OR inside an AND. The child class is how you inject that OR into the parent's AND. Once you see that, nesting stops being an academic feature and becomes the natural way to write any policy whose real intent is "all of these conditions, one of which is a set of choices."
Practical guidance
Keep the nesting shallow and the names meaningful. One level of parent-and-child covers almost every real policy, and the show output stays readable at that depth. Name the child class after the thing it represents (a protocol group, a source set) rather than after the policy that happens to use it first, because the whole point is that other policies will reference it later. And when a nested policy misbehaves, go straight to show policy-map type inspect zone-pair: the indented tree tells you immediately whether the parent is reaching the child and what the child is matching, which is far faster than reasoning about the config by eye.
One caution worth stating plainly: nesting changes where the logic lives, not how much of it there is, so keep the relationship between parent and child obvious in your naming and your documentation. A future engineer reading the policy-map sees a class reference a class-map they then have to go find. If the child is named for what it represents and the parent is named for the decision it makes, that lookup is a two-second confirmation rather than a hunt. Nesting rewards teams that name things well and quietly punishes those who do not, because the indirection that buys you reuse is the same indirection that hides intent when the names are vague.
Key takeaways
- A class-map can match another class-map with
match class-map, letting you build reusable, layered traffic definitions instead of flat one-off lists. - match-any is OR, match-all is AND. Nesting exists to combine them: a match-all parent can require several conditions, one of which is a match-any child (a set of protocol choices).
- The show output proves the hierarchy.
show policy-map type inspect zone-pairprints the parent (match-all), its reference to the child (match-any), and the child's http/https matches, indented to show evaluation order. - Reuse is the first payoff: define a protocol group once as a child class and reference it from every policy, so one edit updates them all.
- Combined logic is the second payoff: "AND (http OR https)" cannot be written in a single flat class-map; a child class injects the OR into the parent's AND.
Nested class-maps are how ZBF policy grows from a demo into something a team can maintain, and they are part of the router-integrated firewall story in the PingLabz Infrastructure Security cluster. To see the full policy engine these classes plug into, return to the Zone-Based Firewall pillar, and for the classification layer beneath them, read up on ZBF port-maps.