Set Up a Basic Firewall on FreeBSD

Last edited on 2026-08-15 Tagged under  #firewall   #freebsd   #bsd   #ssh   #network   #selfhosting 

I use Packet Filter (PF) to set up a basic firewall on FreeBSD. To start, the only service I’m running that requires inbound access is SSH. SSHGuard is added for an additional layer of security.



Start Here

PF requires a ruleset configuration file to operate. Example rulesets are found in /usr/share/examples/pf/.

I implement a basic ruleset by creating the /etc/pf.conf file with:

tcp_services = "{ ssh }"

scrub in all
block in all

pass out all keep state

pass in proto tcp to port $tcp_services

This blocks all inbound traffic by default, with the exception of ssh, and allows all outbound traffic to pass.

Enable PF

Set the pf kernel module to be loaded by adding to /etc/rc.conf:

doas sysrc pf_enable=YES

Add a line which specifies the path to the ruleset file:

doas sysrc pf_rules=/etc/pf.conf

Enable logging support, provided by pflog(4), by adding:

doas sysrc pflog_enable=YES

Reboot.

SSH connections are allowed in. Everything else is blocked. Try running ping directed at the firewalled FreeBSD machine and observe that all packets are dropped.

Use the pfctl(8) command to manage pf. Example: Check /etc/pf.conf for errors, but do not load ruleset:

-> doas pfctl -nvf /etc/pf.conf
tcp_services = "{ ssh }"
scrub in all fragment reassemble
block drop in all
pass out all flags S/SA keep state
pass in proto tcp from any to any port = ssh flags S/SA keep state

To view a dynamic snapshot of traffic that passes through the firewall, install the pftop(8) package:

doas pkg install pftop

SSHGuard

Install sshguard to block bad ssh login attempts:

doas pkg install sshguard

Add to rc.conf:

doas sysrc sshguard_enable=YES

Set the appropriate firewall BACKEND for sshguard by modifying /usr/local/etc/sshguard.conf:

# Full path to backend executable (required, no default)
BACKEND="/usr/local/libexec/sshg-fw-pf"

SSHGuard works alongside pf by adding attackers to the table <sshguard>. Create this table and block attackers by adding the following lines to the evolving /etc/pf.conf:

table <sshguard> persist
.
.
.
block in proto tcp from <sshguard>

NOTE
Ensure the line table <sshguard> persist comes before filtering rules to prevent syntax errors.

See sshguard-setup(7) for more details.

Reload the rules:

doas pfctl -f /etc/pf.conf

Start the service:

doas service sshguard start

To inspect the contents of the block table, run:

doas pfctl -t sshguard -T show

ICMP

Blocking ping can make troubleshooting network issues more difficult. Let’s give it a pass, and allow destination unreachable messages. Add to /etc/pf.conf:

icmp_types = "{ echoreq, unreach }"
.
.
.
pass in inet proto icmp icmp-type $icmp_types

Final Config

The complete /etc/pf.conf:

tcp_services = "{ ssh }"
# 'echoreq' allows inbound ping requests, and 'unreach'
# allows destination unreachable messages
icmp_types = "{ echoreq, unreach }"

# table added before filtering rules to prevent syntax errors
table <sshguard> persist

scrub in all
block in all
block in proto tcp from <sshguard>

pass out all keep state

pass in inet proto icmp icmp-type $icmp_types
pass in proto tcp to port $tcp_services

Reload the rules:

doas pfctl -f /etc/pf.conf

Resources

You can like, share, or comment on this post on the Fediverse 💬

Thanks for reading! Read other posts?

« Previous: Configure SSH for Passwordless Logins to FreeBSD Servers