Skip to main content
RunBook Academy

VyOSXXXVII · Firewall FundamentalsZones and chains

Zones and chains — base chains, named rule-sets, `firewall zone`, and jump targets

Advanced⏱ ~22 minvyosconfigureset firewall ipv4 nameset firewall ipv4 forward filterset firewall zoneshow firewallnft list ruleset

What you'll learn

  • Name the three IPv4 base chains and say which traffic each one sees
  • Write a named rule-set and reach it from a base chain with `action jump`
  • Build a zone with `set firewall zone`, and read the from-direction model correctly
  • Choose between `drop`, `reject` and `return` as a rule-set default-action and know what each does to the packet
  • Diagnose a rule-set that is never reached, and a zone pair whose reverse direction was never written

Prerequisites

Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19

Not yet marked complete on this device.

The VyOS 1.5 firewall has three layers, and almost every firewall problem an operator hits is a confusion between two of them:

  1. Base chains are bound to netfilter hooks and see traffic automatically. There are three that matter for filtering, and which one sees a packet depends entirely on where the packet is going.
  2. Named rule-sets see nothing at all until a base chain sends them something. They are policy you write once and reach from wherever it applies.
  3. Zones are an optional layer on top: group interfaces into trust domains and express policy as “traffic entering this zone from that zone uses this rule-set”.

This lesson builds the three in order, then covers the failure modes — a rule-set that is never reached, a zone pair whose reverse direction nobody wrote, and a default-action that does something other than what the operator assumed.

The three base chains

Filtering happens in three base chains per address family. The router picks one for each packet, by destination:

Base chainSeesTypical content
firewall ipv4 input filterTraffic to the router itself — SSH, BGP, WireGuard listeners, ICMP to a router addressProtect the control plane
firewall ipv4 forward filterTraffic through the router — everything being routed between networksThe transit policy
firewall ipv4 output filterTraffic originated by the router — its own DNS, NTP, monitoringRarely used; useful for egress control on a hardened box

firewall ipv6 input filter, forward filter and output filter are the exact parallels for IPv6, and they are separate. A rule written for IPv4 does nothing to IPv6 traffic, which is the single most common way a “closed” router turns out to be open.

The critical consequence of the table: the interface a packet arrived on does not choose the chain. A packet arriving on the WAN interface goes to input filter if it is addressed to the router and to forward filter if it is being routed onward. If your SSH rule is in forward filter, it will never match anything.

flowchart LR
  P["Packet arrives"] --> R{"Destination"}
  R -->|"a router address"| IN["ipv4 input filter"]
  R -->|"routed onward"| FW["ipv4 forward filter"]
  L["Router originates"] --> OUT["ipv4 output filter"]
  IN --> D1["accept / drop / reject<br/>or jump to a named set"]
  FW --> D2["accept / drop / reject<br/>or jump to a named set"]
  OUT --> D3["accept / drop / reject<br/>or jump to a named set"]

Named rule-sets and the jump

A named rule-set is a chain with no packets in it. It exists under firewall ipv4 name <NAME> (or firewall ipv6 name <NAME>), it holds numbered rules and a default-action, and nothing walks it until a base chain rule says action jump and names it as a jump-target.

configure

# The policy, written once
set firewall ipv4 name WAN-TO-LAN description 'Inbound from the Internet towards the LAN'
set firewall ipv4 name WAN-TO-LAN default-action drop
set firewall ipv4 name WAN-TO-LAN default-log

set firewall ipv4 name WAN-TO-LAN rule 5 action accept
set firewall ipv4 name WAN-TO-LAN rule 5 description 'Return traffic'
set firewall ipv4 name WAN-TO-LAN rule 5 state established
set firewall ipv4 name WAN-TO-LAN rule 5 state related

set firewall ipv4 name WAN-TO-LAN rule 10 action accept
set firewall ipv4 name WAN-TO-LAN rule 10 description 'Published web server'
set firewall ipv4 name WAN-TO-LAN rule 10 protocol tcp
set firewall ipv4 name WAN-TO-LAN rule 10 destination address 192.0.2.10
set firewall ipv4 name WAN-TO-LAN rule 10 destination port 443

set firewall ipv4 name WAN-TO-LAN rule 100 action drop
set firewall ipv4 name WAN-TO-LAN rule 100 description 'Log invalid separately'
set firewall ipv4 name WAN-TO-LAN rule 100 state invalid
set firewall ipv4 name WAN-TO-LAN rule 100 log

# Reaching it from the base chain
set firewall ipv4 forward filter rule 10 action jump
set firewall ipv4 forward filter rule 10 jump-target WAN-TO-LAN
set firewall ipv4 forward filter rule 10 inbound-interface name eth0
set firewall ipv4 forward filter rule 10 outbound-interface name eth1

set firewall ipv4 forward filter default-action drop

commit
save

Note state established and state related as bare values. There is no enable leaf beneath state in 1.4 or later; state is a multi-valued leaf and each value is its own set.

Note also that the base-chain rule carries both the inbound and the outbound interface. That pair is what makes it a direction. A jump rule that names only inbound-interface sends every packet arriving on that interface into the rule-set, regardless of where it is going — which is usually more traffic than the rule-set was written for.

Zones

A zone is a named group of interfaces that share a trust level, and the zone layer lets you write policy in terms of those groups instead of interface pairs. It is optional — the base chain and jump model above is a complete firewall on its own — and it earns its keep when a trust domain spans several interfaces, or when the number of interface pairs has grown past what a person can read.

set firewall zone LAN description 'Internal user networks'
set firewall zone LAN interface eth1
set firewall zone LAN interface eth2
set firewall zone LAN default-action drop

set firewall zone WAN description 'Internet'
set firewall zone WAN interface eth0
set firewall zone WAN default-action drop

set firewall zone DMZ interface eth3
set firewall zone DMZ default-action drop

set firewall zone LOCAL local-zone
set firewall zone LOCAL default-action drop

local-zone marks the zone that represents the router itself rather than a set of interfaces. Traffic destined for a router address enters that zone, which is how zone policy expresses “who may reach SSH and BGP on this box” — the same distinction the input filter base chain makes.

The zone model is written from the destination

This is the part that catches people, and it is the reverse of how most operators say it out loud. Policy hangs off the destination zone and names the source with from:

# Into LAN, from WAN: the inbound Internet policy
set firewall zone LAN from WAN firewall name 'WAN-TO-LAN'

# Into WAN, from LAN: the outbound policy
set firewall zone WAN from LAN firewall name 'LAN-TO-WAN'

# Into DMZ, from WAN: published services only
set firewall zone DMZ from WAN firewall name 'WAN-TO-DMZ'

# Into LOCAL, from LAN: management access to the router
set firewall zone LOCAL from LAN firewall name 'LAN-TO-LOCAL'

Read set firewall zone LAN from WAN firewall name WAN-TO-LAN as: “for traffic arriving into the LAN zone from the WAN zone, use the rule-set WAN-TO-LAN.” IPv6 policy for the same pair goes on the same node with firewall ipv6-name instead of firewall name, and it is a separate rule-set with separate rules.

flowchart LR
  WAN["WAN zone<br/>eth0"] -->|"zone LAN from WAN<br/>WAN-TO-LAN"| LAN["LAN zone<br/>eth1, eth2"]
  LAN -->|"zone WAN from LAN<br/>LAN-TO-WAN"| WAN
  WAN -->|"zone DMZ from WAN<br/>WAN-TO-DMZ"| DMZ["DMZ zone<br/>eth3"]
  LAN -->|"zone LOCAL from LAN<br/>LAN-TO-LOCAL"| LOCAL["LOCAL zone<br/>local-zone"]

Every arrow is one from statement, and each arrow is independent. There are twelve possible arrows between four zones, and this diagram draws four of them. The other eight are governed by the destination zone’s default-action, which is why that value is part of the policy and not a formality.

Intra-zone traffic

Traffic between two interfaces in the same zone is a separate question again, controlled on the zone itself:

set firewall zone LAN intra-zone-filtering action accept

Accepting is the common choice for a zone that groups several access ports of one trust level. When the zone groups things that should not talk to each other — a guest network split across two VLANs, say — point it at a rule-set instead of accepting, and confirm what you got with sudo nft list ruleset rather than assuming.

Default-action and logging

Every base chain and every named rule-set ends in a default-action, and it is the whole policy for anything the rules did not match:

set firewall ipv4 forward filter default-action drop
set firewall ipv4 forward filter default-log

set firewall ipv4 name WAN-TO-LAN default-action drop
set firewall ipv4 name WAN-TO-LAN default-log

default-log — the 1.4+ spelling of what 1.3 called enable-default-log — logs what falls off the end. It is not optional in practice. A chain that drops silently gives you no answer to “why is this traffic not arriving”, which is the question you will be asked at three in the morning.

The state established / state related accept near the top of every rule-set is what keeps a default-drop firewall usable. VyOS also offers a global form under set firewall global-options state-policy, which applies one state decision across every chain at once instead of repeating it. It is convenient and it is broad — a global accept for established traffic applies to paths you were not thinking about — so decide deliberately which of the two you are using, and do not half-do both.

Validating what you built

The configuration and the kernel are two different readings and you want both.

The configuration view:

show configuration commands | match 'firewall zone'
show configuration commands | match 'jump-target'

The counters, which are the evidence that a rule is being reached at all:

Read-only / Saferules and packet counters for one rule-set
vyos@r1:~$ show firewall ipv4 name WAN-TO-LAN

And the kernel view, which is the truth:

sudo nft list ruleset

A rule whose counter never moves is either unreachable or matching nothing. Those are different problems with the same appearance, and the way to tell them apart is to look at what jumps into the chain: if nothing does, the rule-set is orphaned no matter how correct its rules are.

Failure modes

The rule-set is never reached

The most common firewall bug on 1.4 and later, and the direct consequence of the removed per-interface binding. The rule-set is written, it is correct, show firewall prints it — and every counter is zero, because no base chain rule and no zone from statement jumps to it.

Diagnose by working backwards from the rule-set rather than forwards from the packet: find the thing that should be sending it traffic. show configuration commands | match jump-target lists every jump in the configuration; if the rule-set’s name is not in that output and no zone names it, it is orphaned.

The reverse direction was never written

Outbound connections hang. Conntrack has the flows; the return path has no rule-set with an established/related accept, so the destination zone’s default-action drop handles the replies. Write the reverse from statement, and give it a state accept.

The rule is in the wrong base chain

A rule permitting SSH to the router placed in forward filter never matches, because traffic addressed to the router does not traverse the forward hook. The reverse mistake — transit policy in input filter — is equally invisible. Ask “where is this packet going” and let that pick the chain.

The named set drops when it should return

The jump succeeds, the rule-set does not match, its default-action drop kills the packet, and the base-chain rules after the jump never run. If the design intended those later rules to get a chance, the rule-set’s default action should be return.

IPv4 is closed and IPv6 is open

firewall ipv4 ... and firewall ipv6 ... are entirely separate trees. A dual-stacked network with policy written only for IPv4 is open on IPv6, and the hosts will happily prefer IPv6. Every zone pair with a firewall name should also have a firewall ipv6-name, or a documented decision that it does not need one.

A commit is rejected for a missing target

A jump-target, or a zone from ... firewall name, that names a rule-set which does not exist is rejected at commit — the check is run by the firewall configuration script, and it is doing you a favour. Create the rule-set first, or create both in the same candidate: the check runs against the candidate, so both being present at commit time is enough.

Rollback

compare
rollback 1
commit
save

Firewall changes are the case for commit-confirm. A rule that locks you out of the router reverts on its own:

commit-confirm 10

If you can still type after ten minutes, confirm keeps it. If you cannot, the router puts back the previous configuration without you. Deleting a zone that other zones still reference in a from statement fails validation, which is the commit engine protecting a half-removed policy — remove the references first, then the zone.

Production discipline

Cross-course references

  • vyos-xxxvii-01-stateful-vs-stateless covers the conntrack layer that state established depends on.
  • vyos-xxxvii-03-rule-ordering covers ordering inside a rule-set.
  • vyos-xxxvii-05-default-deny covers the reference architecture for a WAN-facing policy.
  • vyos-xli-05-wireguard-firewall is a worked 1.5 example of the base-chain plus jump pattern on a real service.
  • The Linux course’s firewall part covers the nftables chains, hooks and priorities from the host perspective.

Quiz

Knowledge check · 4 questions

  1. Q1. A WAN-facing named rule-set is reached by a jump, contains no rule that matches the traffic, and has `default-action accept`. What is the effective posture?

  2. Q2. A rule-set attached to a zone pair applies to every interface in those zones, so adding an interface to a zone brings it under the existing policy.

  3. Q3. Zones LAN (eth1, eth2) and WAN (eth0) are configured, with `set firewall zone WAN from LAN firewall name LAN-TO-WAN` and `set firewall zone LAN from WAN firewall name WAN-TO-LAN`. A host on eth1 opens a connection to an Internet address. Which rule-set evaluates the outbound packet, and why?

    eth1 and eth2 are in zone LAN; eth0 is in zone WAN. Both zones have `default-action drop`. Both `from` statements above are committed and both rule-sets exist. A LAN host sends the first packet of a new TCP connection to an Internet address, which the routing table sends out of eth0.

  4. Q4. An operator writes `set firewall zone WAN from LAN firewall name LAN-TO-WAN` and stops there. Users report that web browsing hangs rather than failing. Explain the mechanism and the fix.

    Zone LAN holds eth1 with `default-action drop`; zone WAN holds eth0 with `default-action drop`. The only `from` statement in the configuration is the one on the WAN zone. LAN-TO-WAN permits outbound TCP. After the commit, LAN hosts can send but every connection stalls with no response.

Passing score: 75%. Answers are checked in this browser.