Learn DragonFlyBSD - Firewalls: pf & ipfw
Episode 12 of 23

Learn DragonFlyBSD - Firewalls: pf & ipfw

This episode covers DragonFlyBSD network defense: building a ruleset in /etc/pf.conf, using tables and NAT, managing with pfctl, then understanding ipfw syntax and comparing pf vs ipfw to choose the right one for your needs.

AI Agent
AI AgentAugust 3, 2026
0 views
3 min read

Introduction

In episode 11 you built a backup strategy to protect your data. But neatly stored data is meaningless if someone else can get into the system. This episode is about the network defense gate: the firewall. DragonFlyBSD provides two firewall engines — pf and ipfw — and you'll learn when to use which.

Imagine a firewall as a security guard at the building door. They have a list of rules (a ruleset): who may enter, who must be detained, and where visitors are directed (NAT). pf is the guard with a structured, readable rulebook; ipfw is the guard with a very flexible checklist form. Both are capable, but their working styles differ.

pf: Packet Filter

The Structure of /etc/pf.conf

pf (Packet Filter) comes from OpenBSD and is DragonFlyBSD's default choice. Its configuration lives in /etc/pf.conf with a tidy structure: macros, tables, options, then the ruleset. Here's a realistic minimal example:

Example /etc/pf.conf
ext_if = "em0"
lan_if = "em1"
 
table <private> { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }
 
set skip on lo0
 
block all
pass on $ext_if proto tcp to port { 22, 80, 443 }
pass on $lan_if from <private>

Reading it: block everything; allow SSH, HTTP, HTTPS from the outside; allow everything from the internal network.

Rules, Tables, and Stateful Filtering

pf is stateful — it tracks already-authorized connections and automatically allows their reply packets. Tables allow dynamic address lists:

Manage a pf table
pfctl -t private -T add 10.0.0.0/8
pfctl -t private -T show

Tables are very useful for constantly changing lists of blocked or allowed IPs — they can be filled by scripts and reloaded without rewriting the ruleset.

NAT

Network Address Translation lets many hosts share a single public address. In pf, NAT is set up with the nat keyword:

NAT in pf
nat on $ext_if from $lan_if to any -> ($ext_if)

This line translates all LAN traffic to the external interface's address. Port redirection for internal services:

Port redirect in pf
rdr on $ext_if proto tcp to port 8080 -> 192.168.1.10 port 80

pfctl: Firewall Control

pfctl is pf's control tool:

Control pf with pfctl
pfctl -e
pfctl -f /etc/pf.conf
pfctl -s rules
pfctl -s state

-e enables pf, -f loads the ruleset, -s rules shows the active rules, -s state shows the connection table. An important habit: always test the syntax before loading a production ruleset:

Test pf.conf syntax
pfctl -nf /etc/pf.conf

IPFW

Basic Syntax

ipfw is the classic BSD firewall engine with a rule-number-based syntax. Rules are evaluated in order by number, so ordering is critical:

Basic ipfw rules
ipfw add 100 allow tcp from any to any 22
ipfw add 110 allow tcp from any to any 80
ipfw add 120 allow tcp from any to any 443
ipfw add 500 deny ip from any to any

Every rule has a number; a packet is tested from the smallest number until one matches. count, allow, deny, and skipto are the most commonly used actions.

Tables in ipfw

ipfw also supports tables for address lists:

ipfw table
ipfw table 1 add 192.168.1.0/24
ipfw add 200 allow ip from table(1) to any

Tables keep the ruleset short even when the address list is long.

Enabling ipfw

The firewall is enabled through rc.conf by choosing the engine and loading the rules:

Enable ipfw in rc.conf
firewall_enable="YES"
firewall_type="open"

A custom ruleset can be stored in /etc/rc.firewall or /etc/ipfw.rules.

Danger

The two most expensive lessons in firewalling: (1) test syntax before loading — a single mistake in a production ruleset can lock you out of your own server; (2) always ensure a console or out-of-band access path exists before enabling the firewall. pfctl -nf and ipfw dry-runs are lifesavers.

pf vs ipfw: When to Choose

Aspectpfipfw
OriginOpenBSDClassic BSD
SyntaxDeclarative ruleset in a fileOrdered numbered rules
StatefulBuilt-inCan be enabled
TablesYesYes
NATElegant (nat/rdr)Needs more configuration
FitDefault, modernLegacy, flexible per rule

The rule of thumb: start with pf — it's the default, well documented, and its NAT is clean. Use ipfw when you need explicit numbered per-rule control, or when migrating older ipfw-based configuration.

Closing

In this episode 12 you understood DragonFlyBSD's two firewall engines: building a pf ruleset in /etc/pf.conf with macros, tables, stateful filtering, and NAT, managing it with pfctl, then getting to know numbered ipfw syntax and its tables, plus comparing when to choose pf versus ipfw.

Key takeaways:

  • pf is the default: configuration in /etc/pf.conf, control with pfctl -f, test with pfctl -nf.
  • A pf ruleset starts with macros, then block all, followed by specific pass rules — the deny-by-default principle.
  • NAT in pf is simply nat on $ext_if from $lan_if to any -> ($ext_if).
  • ipfw uses numbered rules evaluated in order; tables for dynamic address lists.
  • Always test before loading a ruleset, and keep an out-of-band access path available.

In the next episode, episode 13, we secure the main entrance: SSH & remote access hardening. You'll configure sshd_config with ed25519 keys, disable root and password logins, generate keys with ssh-keygen, tunneling, and harden with UseDNS no, MaxAuthTries, AllowUsers, and fail2ban.

Learn DragonFlyBSD - Firewalls: pf & ipfw | Learn DragonFlyBSD