Homelab — FreeBSD pf Router

A custom edge router running FreeBSD, pf, unbound, and dhcpd

FreeBSD pf Networking Self-Hosted

What This Is

At the edge of my homelab is a small, fanless FreeBSD box that handles every packet coming into and out of my network. It's the WAN router, the firewall, the DHCP and DNS server, the VPN endpoint, and the boundary that everything else sits behind.

I built it instead of buying a turnkey router for the same reason I run NixOS on servers: I want the configuration to be obvious, version-controlled, and learnable. FreeBSD's pf, rc.conf, and tightly integrated networking stack give me exactly that.

Why Not OPNsense or pfSense?

OPNsense and pfSense are both excellent — and both are FreeBSD-based. But they ship a heavyweight web UI on top of FreeBSD, and the moment you click into "Advanced", you discover the UI doesn't expose what you actually want to configure. You SSH in, edit a hidden file, and now your config is split between the GUI and a shadow config you have to remember.

Plain FreeBSD avoids that split. Every line of configuration is in /etc/rc.conf, /etc/pf.conf, /usr/local/etc/unbound/ — readable, greppable, and trivial to put in git. The router is small enough to fit in my head, which is the entire point.

Hardware

Intel NICs matter. Realtek and other budget NICs technically work, but they regularly cost more in debugging time than the BOM saved.

Network Topology

                 ┌─────────────────────┐
                 │      Internet       │
                 └──────────┬──────────┘
                            │ igc0 (WAN)
                 ┌──────────┴──────────┐
                 │   FreeBSD Router    │
                 │   pf · unbound      │
                 │   dhcpd · ntpd      │
                 └──┬─────┬─────┬──────┘
            igc1   │     │     │  igc3
        (LAN trust)│igc2 │     │ (IoT / quarantine)
                   │ (servers)│
        ┌──────────┴───┐ ┌────┴────────┐ ┌──────────┐
        │ Workstations │ │ Homelab     │ │ IoT VLAN │
        │ Phones / TV  │ │ Proxmox/K8s │ │ Cameras  │
        └──────────────┘ └─────────────┘ └──────────┘

Four physical interfaces, four broadcast domains. No VLAN trunking required for the common case — physical separation is the simplest defense against misconfiguration. IoT and trusted LAN traffic never share a wire.

Software Stack

Everything is in base or in pkg. No vendor binaries, no upstream-only repos, no kernel modules I didn't build from source.

The Configuration, in Spirit

# /etc/rc.conf (excerpt)
hostname="gw.lan"
zfs_enable="YES"

# Enable forwarding for both IP versions
gateway_enable="YES"
ipv6_gateway_enable="YES"

# Interfaces
ifconfig_igc0="DHCP"
ifconfig_igc1="inet 10.10.10.1/24"
ifconfig_igc2="inet 10.10.20.1/24"
ifconfig_igc3="inet 10.10.30.1/24"

# Services
pf_enable="YES"
pflog_enable="YES"
local_unbound_enable="YES"
dhcpd_enable="YES"
ntpd_enable="YES"
sshd_enable="YES"

That's the core. Most of the rest is in /etc/pf.conf — see the pf rules deep dive for the full ruleset and the reasoning behind it.

Operational Habits

Articles in This Series

Related Reading

Have questions about the build, or notice something I'd benefit from changing? Drop me a line.