pf in one config

freebsd 15 / published / guide #15 / ≈ 2 min read

pf is FreeBSD's built-in firewall — short for "packet filter." Unlike Linux's iptables/nftables, the entire ruleset lives in a single, readable text file. You write rules in something close to plain English, save, and reload.

What does a firewall actually do? It looks at every packet of network traffic going in or out of your machine and decides whether to allow it. With no firewall, every port your computer listens on is exposed to the network.

Heads up: firewall rules can lock you out of a remote machine. If you're configuring pf over SSH, make absolutely sure you allow port 22 before activating.

Step 1: Write the config file

sudo nano /etc/pf.conf

Replace whatever's there with this starter ruleset. It works for a single-network-card server — we'll walk through what each line does after.

# /etc/pf.conf

ext_if = "em0"
table <bruteforce> persist

scrub in all

block in all
pass out all keep state

pass quick on lo0 all
pass inet proto icmp icmp-type echoreq

pass in on $ext_if proto tcp to ($ext_if) port 22 
    keep state 
    (max-src-conn 10, max-src-conn-rate 5/60, 
     overload <bruteforce> flush global)

block quick from <bruteforce>

# uncomment for a web server:
# pass in on $ext_if proto tcp to ($ext_if) port { 80, 443 }

What each block means

  • ext_if = "em0" — a variable for your network interface name. Find yours with ifconfig.
  • table <bruteforce> — a list we'll fill with IPs that hammer our SSH port.
  • scrub in all — normalises incoming packets (drops malformed ones).
  • block in all — default policy: block everything inbound.
  • pass out all keep state — your machine can talk out freely.
  • pass quick on lo0 all — loopback traffic (programs talking to themselves) is allowed.
  • pass inet proto icmp icmp-type echoreq — allow incoming ping. Helpful for debugging.
  • The big SSH block — allow port 22, but if any single IP opens more than 10 connections or 5 in 60 seconds, banish it to <bruteforce>.
  • block quick from <bruteforce> — block everything from banished IPs.

Step 2: Enable pf at boot

sudo sysrc pf_enable="YES"
sudo sysrc pflog_enable="YES"

pflog is the logging companion — it gives you a virtual interface (pflog0) you can run tcpdump against.

Step 3: Start it

sudo service pf start
sudo service pflog start

Step 4: Reload after edits

Once pf is running, every config change just needs a reload:

sudo pfctl -f /etc/pf.conf

If there's a syntax error, pf prints it and keeps the previous rules — it never strands you with no firewall.

Inspecting what's happening

sudo pfctl -s rules                  # show loaded rules
sudo pfctl -s states                 # show currently-allowed connections
sudo pfctl -t bruteforce -T show     # who has been banished
sudo tcpdump -nettti pflog0          # watch blocked traffic in real time

The panic button

If you've locked yourself out and you're at a local console:

sudo pfctl -d

That disables pf entirely until you re-enable it. Use as a last resort.

← back to index