VyOSXXXVII · Firewall FundamentalsZones and chains
Zones and chains — base chains, named rule-sets, `firewall zone`, and jump targets
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
The VyOS 1.5 firewall has three layers, and almost every firewall problem an operator hits is a confusion between two of them:
- 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.
- 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.
- 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 chain | Sees | Typical content |
|---|---|---|
firewall ipv4 input filter | Traffic to the router itself — SSH, BGP, WireGuard listeners, ICMP to a router address | Protect the control plane |
firewall ipv4 forward filter | Traffic through the router — everything being routed between networks | The transit policy |
firewall ipv4 output filter | Traffic originated by the router — its own DNS, NTP, monitoring | Rarely 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:
vyos@r1:~$ show firewall ipv4 name WAN-TO-LANAnd 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-statelesscovers the conntrack layer thatstate establisheddepends on.vyos-xxxvii-03-rule-orderingcovers ordering inside a rule-set.vyos-xxxvii-05-default-denycovers the reference architecture for a WAN-facing policy.vyos-xli-05-wireguard-firewallis 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
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?
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.
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.
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.