FreeBSD pf Configuration for an Internet-Facing Host

Whenever running an internet-facing host, it is a good idea to lock down network traffic incoming to the host, as a security precaution. My main reason for running pf is to counter ssh bruteforce attempts. The following steps outline a simple configuration of the pf firewall.

Note: This has been tested on FreeBSD 14.1.

pf Configuration Preparation

For preparation, check IPv4 and IPv6 public internet connectivity, with ping. This is to ensure that networking works. If then the network stack is down while pf is running, then pf is the cause of this.

ping -4 -c4 www.freebsd.org
ping -6 -c4 www.freebsd.org

Write pf Configuration File.

The following pf configuration file sets up pf to perform these tasks:

doas vim /etc/pf.conf

public_if = "vtnet0"
table <ssh_bruteforce> persist

# not routable address ranges, according to RFC6890
table <not_routable> { 0.0.0.0/8 10.0.0.0/8 \ 
                       100.64.0.0/10 127.0.0.0/8 \ 
                       169.254.0.0/16 172.16.0.0/12 \
                       192.0.0.0/24 192.0.0.0/29 \
                       192.0.2.0/24 192.88.99.0/24 \
                       192.168.0.0/16 198.18.0.0/15 \
                       198.51.100.0/24 203.0.113.0/24 \
                       240.0.0.0/4 255.255.255.255/32 }

# allow all traffic on loopback interface
set skip on lo0

# assemble fragmented packets
scrub in all fragment reassemble

# Block address spoofing
antispoof quick for $public_if

# Not routable address ranges, should never pass the public interface
block in quick on $public_if from <not_routable>
block return out quick on egress to <not_routable>

# default to block all traffic
block all

# allow SSH in, with brute force scanner protection
pass in on $public_if proto tcp to port 22 \
    keep state (max-src-conn 5, max-src-conn-rate 1/5, \
    overload <ssh_bruteforce> flush global)

# allow SSH, DNS, HTTP, NTP, HTTPS out
pass out proto { tcp udp } to port { 22 53 80 123 443 }

# allow ICMP in and out
pass inet proto icmp
pass inet6 proto icmp6

Enable pf

The following commands:

doas sysrc pf_enable="YES"
doas pfctl -nf /etc/pf.conf
doas service pf start
ping -4 -c4 www.freebsd.org
ping -6 -c4 www.freebsd.org

This should result in a bootable FreeBSD host, with public internet connectivity, protected by the pf firewall.

Done.

pf Maintenance

pf maintenance commands:

Add a crontab entry to periodically clean the SSH bruteforce table: doas crontab -e, add @daily /sbin/pfctl -t ssh_bruteforce -T expire 172800. 172.800 seconds = 60 seconds * 60 minutes * 48 hours, is the duration to keep IP addresses in the table.