Building a NetBSD firewall: writing the npf.conf ruleset, enabling NAT, managing with npfctl, understanding ipfilter as a legacy option, and comparing it with pf.

In episode 11 we protected data with backups. But defense doesn't stop at storage — there's also a wall to build on the network side. In this episode we'll build the NetBSD firewall with npf and ipfilter — writing a ruleset in npf.conf, enabling NAT, managing with npfctl, understanding ipfilter as a legacy option, and comparing it with pf from OpenBSD.
Before writing rules, understand three core concepts:
| Concept | Explanation |
|---|---|
| Rule | A rule that matches traffic (protocol, port, direction) |
| Policy | The default stance: block or pass |
| Stateful | The firewall remembers already-allowed connections |
The safest design principle: default deny — block everything, then allow only what's needed. This is npf's foundation.
npf is a stateful firewall developed specifically for NetBSD. Its strengths: clean syntax, mature NAT and IPv6 support, and rules that can be reloaded without dropping connections. NPF is built as a kernel module managed with npfctl — and we saw it as the npf module in episode 9.
The ruleset is written in /etc/npf.conf. A complete example for a server:
$if = "wm0"
$ext_ip = 192.168.1.50
set default: in block all
set default: out block all
group default {
# Loopback is free
pass on lo0 all
# Outbound traffic is allowed
pass out on $if inet family 4 proto { tcp, udp, icmp } \
from $ext_ip to any
# Inbound SSH
pass in on $if inet proto tcp from any to $ext_ip port 22 \
flags S/SA keep state
# HTTP and HTTPS
pass in on $if inet proto tcp from any to $ext_ip port { 80, 443 } \
flags S/SA keep state
}
# Outbound NAT for the internal network
group "external" on $if {
nat $if inet from 192.168.1.0/24 to any
}Let's break down the important parts:
| Line | Meaning |
|---|---|
set default: in block all | Default deny for all inbound traffic |
set default: out block all | Default deny for all outbound traffic |
pass on lo0 all | Loopback fully allowed |
pass out ... keep state | Outbound connections allowed and remembered |
pass in ... port 22 flags S/SA keep state | Only new SSH connections may enter |
nat $if ... to any | NAT for the internal network |
The flags S/SA keep state option ensures only new connections (SYN) are allowed in; their follow-on connections are recognized from the state table.
Info
The $if and $ext_ip variables at the top of the file make the ruleset easy to move between machines — just change those two lines, not the whole ruleset. This is the recommended pattern for centralized configuration.
After writing the file, activate the firewall:
npfctl startNPF is now active.Reload the ruleset after changing npf.conf — without dropping connections:
npfctl reloadViewing statistics and the active ruleset:
npfctl statsInterface wm0: 15 packets passed, 3 packets blockedTo stop it temporarily:
npfctl stopBefore loading a ruleset onto a production system, validate its syntax:
npfctl validate /etc/npf.confConfiguration is valid.Enable it through the rc system (remember the pattern from episode 6):
npf=YES
npf_flags=""At boot, NetBSD will run /etc/rc.d/npf and load /etc/npf.conf.
IPFilter is a classic BSD firewall also available on NetBSD. It's managed with ipf, ipfstat, and ipnat (for NAT):
ipfstat -ipass in quick on wm0 proto tcp from any to 192.168.1.50 port = 22
pass in quick on wm0 proto tcp from any to 192.168.1.50 port = 80ipf rules are written in /etc/ipf.conf and loaded with:
ipf -Fa -f /etc/ipf.confWarning
IPFilter is still supported on NetBSD, but for new projects npf is the recommended choice — it's modern, native to NetBSD, and actively developed. IPFilter is here for compatibility and legacy systems.
NetBSD supports three firewalls to choose from:
| Firewall | Origin | Strengths | Weaknesses |
|---|---|---|---|
| npf | NetBSD | Modern, mature NAT+IPv6, reload without drops | Fewer online examples |
| ipfilter | Separate project | Stable, feature-rich, plenty of documentation | Legacy, older syntax |
| pf | OpenBSD | Proven across many systems | Less maintained on NetBSD than npf |
| Aspect | npf | ipfilter | pf |
|---|---|---|---|
| Syntax | Clean, pf-like | Verbose | Iconic OpenBSD |
| NAT | nat in the ruleset | Separate ipnat | nat in pf.conf |
| IPv6 | Full support | Good support | Full support |
| NetBSD recommendation | Primary | Legacy | Alternative |
For those just starting, npf is the most fitting choice — one language for rules, NAT, and IPv6.
When connections don't work, check in order:
| Step | Command |
|---|---|
| Is npf active? | npfctl show |
| Are packets being blocked? | npfctl stats |
| Which ruleset matches? | npfctl list |
| Firewall log | Check the kernel log with dmesg |
npfctl listIn this episode 12, you've built NetBSD's defensive wall: writing an npf.conf ruleset with the default deny principle, enabling NAT, managing the firewall with npfctl, understanding ipfilter as a legacy option, and mapping out the comparison between npf, ipfilter, and pf.
Key takeaways:
/etc/npf.conf; manage it with npfctl start/reload/stop/validate.nat $if inet from <net> to any.npf=YES in /etc/rc.conf.In the next episode, episode 13, we'll secure the administration door: SSH and remote access hardening — tuning sshd_config for ed25519 keys, disabling root and password login, and securing access with fail2ban. See you in episode 13!