I’ve written before about specific things VyOS can do, including site-to-site WireGuard over OSPF, a road-warrior VPN, and redirecting captive DNS. I’ve never actually shown the VyOS firewall rules that sit in front of all of it. That’s the part that actually decides what gets in and what doesn’t, so it’s overdue.

What follows is the firewall configuration pattern I run across my gateways. It is pulled directly from the routers, then generalised where something was specific to my network and wouldn’t mean anything to you. I’m also flagging a real syntax change in VyOS’s zone-based firewall that isn’t reflected in the current documentation as of writing, which I only found because one of my routers is on a newer release than the rest.

The version gap that started this Link to heading

Most of my gateways run VyOS 1.4 (Sagitta), which is showing its age now that 1.5 (Circinus) is the LTS release. One router, though, is on a 2026.03 rolling build. When I went to compare firewall configs across the fleet to write this post, the zone configuration on the rolling-release box didn’t match what I had everywhere else, and it didn’t match the current docs either. More on that below, as it’s worth knowing about before you copy any of this.

Global hardening options Link to heading

Every router starts with the same baseline network hardening, independent of anything zone- or rule-specific:

set firewall global-options all-ping 'enable'
set firewall global-options broadcast-ping 'disable'
set firewall global-options ip-src-route 'disable'
set firewall global-options ipv6-receive-redirects 'disable'
set firewall global-options ipv6-src-route 'disable'
set firewall global-options log-martians 'enable'
set firewall global-options receive-redirects 'disable'
set firewall global-options send-redirects 'disable'
set firewall global-options source-validation 'disable'
set firewall global-options syn-cookies 'enable'
set firewall global-options twa-hazards-protection 'enable'

The two worth calling out: syn-cookies gives you basic SYN-flood resistance for free, and log-martians logs packets with impossible source/destination addresses, which is usually the first sign something’s misconfigured (or someone’s spoofing) rather than something malicious in itself.

Groups: don’t repeat yourself in rules Link to heading

Before the rules, the groups they reference. VyOS firewall groups let you define a set of addresses, networks, or interfaces once and reuse them across rules, so a change in one place propagates everywhere:

set firewall group network-group internal-subnets network '10.254.88.0/21'
set firewall group network-group internal-subnets network '10.254.112.0/21'

set firewall group address-group dns-servers address '10.254.95.3'
set firewall group address-group dns-servers address '10.254.95.4'

set firewall group interface-group IG_VPN interface 'wg0'
set firewall group interface-group IG_VPN interface 'wg1'

I also keep an empty network-group reserved for a threat blocklist, populated by a scheduled job that pulls a public IP-reputation feed and rewrites the group’s members:

set firewall group network-group threats-blocklist description 'Threats Blocklist'

Referencing threats-blocklist in a rule (see below) means the rule logic never has to change. Only the group’s contents do, on a schedule, outside the firewall config entirely.

The zone model Link to heading

Rather than writing input/output/forward rules per interface, I use VyOS’s zone-based firewall. Interfaces get assigned to a zone, and you write rules for zone-to-zone traffic rather than interface-to-interface. Mine breaks down as:

  • LOCAL: the router itself (the local-zone)
  • WAN: the internet-facing interface
  • VPN: WireGuard interfaces
  • INTERNAL: the LAN side (everything behind the router on trusted internal segments)
flowchart LR
    WAN((WAN)) <--> LOCAL((LOCAL))
    VPN((VPN)) <--> LOCAL
    WAN <--> VPN
    INTERNAL((INTERNAL / LAN)) <--> LOCAL
    INTERNAL <--> WAN
    INTERNAL <--> VPN

Every zone pair that’s allowed to talk gets an explicit named ruleset, and every zone has a default-action of drop. Nothing passes between zones unless a rule says so:

set firewall zone LOCAL default-action 'drop'
set firewall zone LOCAL from WAN firewall name 'WAN-LOCAL'
set firewall zone LOCAL from VPN firewall name 'VPN-LOCAL'
set firewall zone LOCAL from INTERNAL firewall name 'INTERNAL-LOCAL'
set firewall zone LOCAL local-zone

set firewall zone WAN default-action 'drop'
set firewall zone WAN from LOCAL firewall name 'LOCAL-WAN'
set firewall zone WAN from VPN firewall name 'VPN-WAN'
set firewall zone WAN from INTERNAL firewall name 'INTERNAL-WAN'
set firewall zone WAN interface 'eth0'

set firewall zone VPN default-action 'drop'
set firewall zone VPN from LOCAL firewall name 'LOCAL-VPN'
set firewall zone VPN from WAN firewall name 'WAN-VPN'
set firewall zone VPN from INTERNAL firewall name 'INTERNAL-VPN'
set firewall zone VPN interface 'wg0'
set firewall zone VPN interface 'wg1'

set firewall zone INTERNAL default-action 'drop'
set firewall zone INTERNAL from LOCAL firewall name 'LOCAL-INTERNAL'
set firewall zone INTERNAL from WAN firewall name 'WAN-INTERNAL'
set firewall zone INTERNAL from VPN firewall name 'VPN-INTERNAL'
set firewall zone INTERNAL interface 'eth1'

That’s four zones, so twelve possible directions of travel. However, only the ones you explicitly wire up with a from clause are reachable at all; everything else hits each zone’s default-action 'drop' with no rule to even evaluate.

Most of these named rulesets (LOCAL-WAN, VPN-LOCAL, LOCAL-VPN, WAN-VPN, LOCAL-INTERNAL, INTERNAL-LOCAL, INTERNAL-VPN, VPN-INTERNAL, INTERNAL-WAN) are intentionally boring. They default to accept and only drop packets in an invalid connection-tracking state:

set firewall ipv4 name LOCAL-WAN default-action 'accept'
set firewall ipv4 name LOCAL-WAN rule 10 action 'drop'
set firewall ipv4 name LOCAL-WAN rule 10 description 'Drop Invalid State'
set firewall ipv4 name LOCAL-WAN rule 10 state 'invalid'

Repeat that pattern for every zone pair where you trust the traffic direction but still want basic conntrack sanity. The two rulesets that actually do real work are WAN-LOCAL (traffic from the internet to the router itself) and, if you segment your LAN into more and less trusted subnets, WAN-INTERNAL (traffic from the internet trying to reach hosts behind the router). I’ll focus on WAN-LOCAL below since it’s the one every VyOS router needs regardless of how you’ve split up your internal network.

WAN-LOCAL: the ruleset that matters Link to heading

This is default-drop with logging, and everything after that is an explicit allow:

set firewall ipv4 name WAN-LOCAL default-action 'drop'
set firewall ipv4 name WAN-LOCAL default-log
set firewall ipv4 name WAN-LOCAL description 'Inbound from the Internet'

# Allow ICMP (useful for diagnostics, low risk)
set firewall ipv4 name WAN-LOCAL rule 10 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 10 description 'Allow Inbound ICMP'
set firewall ipv4 name WAN-LOCAL rule 10 protocol 'icmp'

# Established/related sessions
set firewall ipv4 name WAN-LOCAL rule 15 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 15 description 'Allow Established Sessions'
set firewall ipv4 name WAN-LOCAL rule 15 protocol 'all'
set firewall ipv4 name WAN-LOCAL rule 15 state 'established'
set firewall ipv4 name WAN-LOCAL rule 15 state 'related'

# Invalid conntrack state (drop before it wastes any more rule evaluation)
set firewall ipv4 name WAN-LOCAL rule 20 action 'drop'
set firewall ipv4 name WAN-LOCAL rule 20 description 'Drop Invalid State'
set firewall ipv4 name WAN-LOCAL rule 20 protocol 'all'
set firewall ipv4 name WAN-LOCAL rule 20 state 'invalid'

# Known-bad sources, from the scheduled blocklist group
set firewall ipv4 name WAN-LOCAL rule 30 action 'drop'
set firewall ipv4 name WAN-LOCAL rule 30 description 'Drop Threats'
set firewall ipv4 name WAN-LOCAL rule 30 log
set firewall ipv4 name WAN-LOCAL rule 30 source group network-group 'threats-blocklist'

# Noise that doesn't deserve a place in the logs
set firewall ipv4 name WAN-LOCAL rule 70 action 'drop'
set firewall ipv4 name WAN-LOCAL rule 70 description 'Drop NetBIOS Name Service Packets'
set firewall ipv4 name WAN-LOCAL rule 70 destination port '137'
set firewall ipv4 name WAN-LOCAL rule 70 protocol 'udp'

set firewall ipv4 name WAN-LOCAL rule 75 action 'drop'
set firewall ipv4 name WAN-LOCAL rule 75 description 'Drop NetBIOS Datagram Service Packets'
set firewall ipv4 name WAN-LOCAL rule 75 destination port '138'
set firewall ipv4 name WAN-LOCAL rule 75 protocol 'udp'

set firewall ipv4 name WAN-LOCAL rule 80 action 'drop'
set firewall ipv4 name WAN-LOCAL rule 80 description 'Drop Telnet Noise'
set firewall ipv4 name WAN-LOCAL rule 80 destination port '23'
set firewall ipv4 name WAN-LOCAL rule 80 protocol 'tcp'

# The actual allow-list
set firewall ipv4 name WAN-LOCAL rule 1000 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 1000 description 'Allow SSH'
set firewall ipv4 name WAN-LOCAL rule 1000 destination port '22'
set firewall ipv4 name WAN-LOCAL rule 1000 protocol 'tcp'

set firewall ipv4 name WAN-LOCAL rule 1010 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 1010 description 'Allow HTTP'
set firewall ipv4 name WAN-LOCAL rule 1010 destination port '80'
set firewall ipv4 name WAN-LOCAL rule 1010 protocol 'tcp'

set firewall ipv4 name WAN-LOCAL rule 1011 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 1011 description 'Allow HTTPS'
set firewall ipv4 name WAN-LOCAL rule 1011 destination port '443'
set firewall ipv4 name WAN-LOCAL rule 1011 protocol 'tcp'

set firewall ipv4 name WAN-LOCAL rule 1012 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 1012 description 'Allow Mail Submission'
set firewall ipv4 name WAN-LOCAL rule 1012 destination port '587'
set firewall ipv4 name WAN-LOCAL rule 1012 protocol 'tcp'

set firewall ipv4 name WAN-LOCAL rule 1013 action 'accept'
set firewall ipv4 name WAN-LOCAL rule 1013 description 'Allow Mail IMAP'
set firewall ipv4 name WAN-LOCAL rule 1013 destination port '143'
set firewall ipv4 name WAN-LOCAL rule 1013 protocol 'tcp'

A few decisions worth explaining rather than just showing:

  • Rule numbering leaves gaps deliberately. 10, 15, 20, 30, 70, 75, 80, then a jump to 1000+ for the actual service allow-list. That gap exists so I can insert a new noise-drop or hardening rule later without renumbering everything downstream.
  • The noise drops (70/75/80) aren’t security controls. NetBIOS and Telnet probes get dropped by the default-deny anyway. Giving them their own silent-drop rules just keeps default-log from filling up with the same handful of scanner signatures every day.
  • WireGuard’s listening ports get their own accept rules, same pattern as 1000+, just on UDP instead of TCP. I’ve left them out above since the exact ports are covered in my road-warrior VPN post.

If you’re running services behind more than one public IP on the same WAN interface, you can scope any of these rules to a specific destination address rather than “any address on this interface”:

set firewall ipv4 name WAN-LOCAL rule 1000 destination address '203.0.113.10/32'

That’s the difference between “SSH is open on this router” and “SSH is open on this router, but only if you’re hitting this specific IP”. This is useful once you’ve got more than one service or more than one public IP to manage on the same box.

The syntax change the docs don’t mention yet Link to heading

Here’s the discrepancy that started this post. On my 1.4 (Sagitta) routers, zone membership is a flat command:

set firewall zone VPN interface 'wg0'

On the router running the 2026.03 rolling build, the exact same logical configuration renders as:

set firewall zone VPN member interface 'wg0'

I checked this two ways: reading the raw config tree on the router, and running show configuration commands interactively to get the canonical set-command form. Both agree that member is real, not a display quirk. I also checked the current published documentation (both the latest and rolling branches on docs.vyos.io) and, as of writing, neither mentions member at all. They still document the flat set firewall zone <name> interface <interface> form.

The explanation turned up in VyOS’s own development tracker: T6841, “Separate interface and VRF options in firewall zone configuration.” It landed in 1.5 (Circinus) and the rolling release, and restructures zone membership so a zone can be tied to a VRF as well as a plain interface. This needs its own node in the config tree rather than overloading interface. Migration scripts handle the rewrite automatically when you upgrade a router in place. What they don’t handle is any Ansible playbook, template, or copy-pasted set command you’re maintaining outside the router. Those will silently target the old schema and either no-op or throw a parse error, depending on how your tooling handles unknown paths.

If you manage more than one VyOS box and they’re not all on the same release, don’t assume config is portable between them. Pull show configuration commands from the actual router before you template anything, rather than trusting docs or an older box’s config as ground truth. This is exactly the kind of gap where the official reference lags the shipped software, since VyOS’s docs and rolling builds are versioned separately.

Adapting these VyOS firewall rules for your own network Link to heading

None of the above will work by copy-pasting as-is. It shouldn’t, since it’s tied to my addressing and my services. To adapt it:

  • Swap 10.254.88.0/21 and friends for your own internal ranges in the network-group definitions.
  • Decide what actually needs a WAN-LOCAL allow rule. If you’re not running SMTP or IMAP publicly, drop rules 1012 and 1013 entirely. Every open port is something you have to keep patched.
  • If you don’t have a threat-intelligence feed populating a blocklist group, that rule is inert but harmless; either wire one up or remove it.
  • Start every new zone-pair ruleset with default-action accept plus the invalid-state drop, and only tighten to default-action drop for zones actually facing untrusted networks. Default-accept everywhere defeats the purpose of zones in the first place.

Further reading Link to heading

This sits alongside the rest of my VyOS series: site-to-site WireGuard with OSPF, the road-warrior VPN setup, and redirecting hardcoded DNS. If you’re running VyOS at a scale where firewall drift between routers on different releases is becoming an operational risk rather than a curiosity, that’s exactly the kind of platform work I do at Intahnet Solutions. More on my background is available at hamzahkhan.com/hamzah-khan.

Related Posts