BSD vs. Linux Firewalling

If you've used pf on OpenBSD and then had to write iptables rules on Linux, the culture shock is real. The tools reflect the different philosophies of the systems they come from — BSD tends toward integrated, opinionated design; Linux tends toward modularity and flexibility, which sometimes means more pieces to understand. Here's how they compare in practice.

The Tools at a Glance

OpenBSD pfFreeBSD ipfwLinux nftables
IntegrationTightly built into OpenBSD; also on FreeBSDNative to FreeBSDLinux kernel; works across all distros
SyntaxClean, human-readable pf.confCommand-line oriented, less intuitiveModern and consistent; steeper initial curve
StatefulWith keep state — explicit and clearWith keep-state flagVia conntrack; ct state matching
NATBuilt-in, clean nat syntaxBuilt-in via ipnat or divertVia nat table hooks
Traffic shapingALTQ (built-in)dummynet (built-in)tc (separate tool entirely)
Default policyset block-policy dropRule 65535 catch-allChain policy setting

Syntax Comparison: The Same Rule Three Ways

Allow SSH in, block everything else:

OpenBSD pf

block all
pass in on egress proto tcp to port 22 keep state

This reads almost like a sentence. block all is the default deny. pass in punches the specific hole. egress is a shorthand for the default route interface — you don't even have to name it.

FreeBSD ipfw

ipfw add 100 allow tcp from any to any 22 in keep-state
ipfw add 65535 deny all from any to any

ipfw processes rules by number, lowest first. The 65535 deny-all is the catch-all at the end. Rule ordering is your responsibility to manage manually — easy to make gaps or conflicts as rulesets grow.

Linux nftables

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        tcp dport 22 ct state new accept
        ct state established,related accept
    }
}

nftables groups rules into tables and chains — more verbose upfront, but scales better for complex rulesets. Tables for different purposes (IPv4, IPv6, NAT) can coexist cleanly without interfering.

Stateful Filtering

# pf — keep state inline on the pass rule
pass in proto tcp to port 80 keep state

# ipfw — keep-state flag at the end
ipfw add allow tcp from any to any 80 keep-state

# nftables — conntrack matching is a separate step
tcp dport 80 ct state new accept      # allow new connections to port 80
ct state established,related accept    # allow return traffic (applies to all)

The nftables approach of separating "allow established" from "allow new" is actually clearer once you're used to it. pf's inline keep state is more compact. Both correctly handle return traffic automatically.

NAT Comparison

# pf — masquerade the internal network behind the external interface
nat on egress from 192.168.1.0/24 to any -> (egress)

# nftables — hook-based approach
table ip nat {
    chain postrouting {
        type nat hook postrouting priority 100;
        ip saddr 192.168.1.0/24 oifname "eth0" masquerade
    }
}

pf's NAT syntax is simpler for standard use. nftables' hook-based approach is more explicit about where in the packet processing pipeline the rule applies — which matters for complex setups, not for masquerading a home network.

Rule First-Match vs. Last-Match

This is the biggest gotcha when switching between systems:

SystemEvaluation modelOverride behaviour
pfLast matching rule winsUse quick to exit on first match
ipfwFirst matching rule wins (by rule number)Lower-numbered rules take precedence
nftablesFirst matching rule in chain winsUse continue to keep evaluating
iptablesFirst matching rule winsUse jump to other chains for exceptions

Which to Use

This is mostly decided by the OS:

  • OpenBSD: Use pf — it's deeply integrated and the rest of the system (authpf, relayd, ospfd) assumes it.
  • FreeBSD: You have a choice. pf is available and well-supported; most people prefer it over ipfw for new setups.
  • Linux: Use nftables, or ufw/firewalld on top of it if you want a simpler interface. iptables still works but new development is in nftables.

The philosophical difference is real but rarely matters in practice for a single-server setup. BSD firewalls feel more opinionated and integrated; Linux firewalls feel more like infrastructure you assemble. Both get the job done.

References