Use a raspberry-pi 2 as a firewall with FreeBSD

10 minute read

Updated @ Mon Nov 16 08:16:30 PM CET 2020: Corrected the version when OPNsense dropped 32 bits support.


I was using OPNsense on my pcengines alix firewall and was quite happy with it.

The alix 2d13 is a nice motherboard with a Geode CPU, it has a 32 bits x86 instruction set. I migrated to OPNsense from pfSense when pfSense dropped 32 bits support.

Unfortunately, OPNsense also dropped support for 32 bits CPU’s in the 19.1.7 release 20.7 release. I decided to install FreeBSD on the alix to use it as my firewall. But I need a temporary firewall solution so I can install FreeBSD on my alix board. I have a Raspberry PI 2 that I wasn’t using.

You’ll find my journey to use my RPI2 as my firewall below.


Install FreeBSD

ARM is a Tier 2 architecture on FreeBSD. Tier 2 architectures are less mature and aren’t supported as well. FreeBSD-update isn’t supported on a Tier 2 architecture for example.

Pkgbase can be an alternative to update FreeBSD on ARM. But I didn’t try this (yet).

Download FreeBSD

Links to latest downloads are available at: Download the latest image and verify the checksum.

[staf@snuffel ~/Downloads]$ sha256 FreeBSD-12.2-PRERELEASE-arm-armv7-RPI2-20200910-r365545.img.xz
SHA256 (FreeBSD-12.2-PRERELEASE-arm-armv7-RPI2-20200910-r365545.img.xz) = e2987f1ae57db48719d484af293fbcc80e944c2dd30c7d3edfae4ac62d8dc3e3
[staf@snuffel ~/Downloads]$ grep e2987f1ae57db48719d484af293fbcc80e944c2dd30c7d3edfae4ac62d8dc3e3 CHECKSUM.SHA256-FreeBSD-12.2-PRERELEASE-arm-armv7-RPI2-20200910-r365545
SHA256 (FreeBSD-12.2-PRERELEASE-arm-armv7-RPI2-20200910-r365545.img.xz) = e2987f1ae57db48719d484af293fbcc80e944c2dd30c7d3edfae4ac62d8dc3e3

Write the image to your sdcard

After downloading the image, you need to write the image to a sdcard. I execute the steps below on a FreeBSD system.

Find your sdcard

The easiest way to find your sdcard is the geom utility. Execute geom disk list to find the device name of your sd card.

root@snuffel:~ # geom disk list
Geom name: cd0
1. Name: cd0
   Mediasize: 0 (0B)
   Sectorsize: 2048
   Mode: r0w0e0
   ident: (null)
   rotationrate: unknown
   fwsectors: 0
   fwheads: 0


Geom name: da0
1. Name: da0
   Mediasize: 7948206080 (7.4G)
   Sectorsize: 512
   Mode: r0w0e0
   descr: Generic STORAGE DEVICE
   lunname: Generic STORAGE DEVICE-4
   lunid: Generic STORAGE DEVICE-4
   ident: 000000000903
   rotationrate: unknown
   fwsectors: 63
   fwheads: 255

root@snuffel:/home/staf/Downloads # 

Write the image to your sdcard

root@snuffel:/home/staf/Downloads # xzcat FreeBSD-12.2-PRERELEASE-arm-armv7-RPI2-20200910-r365545.img.xz | dd of=/dev/da0 status=progress bs=1M
  3220176896 bytes (3220 MB, 3071 MiB) transferred 694.026s, 4640 kB/s
0+370017 records in
3072+0 records out
3221225472 bytes transferred in 694.681604 secs (4636981 bytes/sec)
root@snuffel:/home/staf/Downloads # sync
root@snuffel:/home/staf/Downloads # 

Boot your raspberry-pi

Boot your raspberry-pi.

Update password

The default password for the root account is root. Always a good idea to change the default password.

Changing local password for root
New Password:
Retype New Password:
root@generic:~ # 

Remove the freebsd user

There is default user freebsd, I’ll set my own user. Use rmuser to remove it.

root@pifire001:~ # rmuser freebsd
Matching password entry:

freebsd:******:1001::0:0:FreeBSD User:/home/freebsd:/bin/csh

Is this the entry you wish to remove? y
Remove user's home directory (/home/freebsd)? y
Removing user (freebsd): mailspool home passwd.
root@pifire001:~ #

Create user

Create a user to administer your firewall. Make sure that you add the user to the wheel group. Only users that are in the wheel group are able to use su on FreeBSD.

root@generic:~ # adduser 
Username: staf
Full name: 
Uid (Leave empty for default): 
Login group [staf]: 
Login group is staf. Invite staf into other groups? []: wheel 
Login class [default]: 
Shell (sh csh tcsh nologin) [sh]: 
Home directory [/home/staf]: 
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]: 
Use a random password? (yes/no) [no]: 
Enter password: 
Enter password again: 
Lock out the account after creation? [no]: no
Username   : staf
Password   : *****
Full Name  : 
Uid        : 1002
Class      : 
Groups     : staf wheel
Home       : /home/staf
Home Mode  : 
Shell      : /bin/sh
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (staf) to the user database.
Add another user? (yes/no): no
root@generic:~ # 

Set your hostname

On FreeBSD system settings are configure in /etc/rc.conf. sysrc is a handy utility to manage it. Use sysrc hostname=your_hostname to change it.

root@generic:~ # sysrc hostname=pifire001
hostname: generic -> pifire001
root@generic:~ # 

To make hostname active without a reboot use hostname your_hostname.

root@generic:~ # hostname pifire001

Install packages

Install the packages that are required to manage your system. I use ansible to manage my systems; sudo and python are required for ansible.

Install pkg
root@generic:~ # pkg
The package management tool is not yet installed on your system.
Do you want to fetch and install it now? [y/N]: y
Bootstrapping pkg from pkg+, please wait...
Verifying signature with trusted certificate done
Installing pkg-1.15.10...
Extracting pkg-1.15.10: 100%
pkg: not enough arguments
Usage: pkg [-v] [-d] [-l] [-N] [-j <jail name or id>|-c <chroot path>|-r <rootdir>] [-C <configuration file>] [-R <repo config dir>] [-o var=value] [-4|-6] <command> [<args>]

For more information on available commands and options see 'pkg help'.
root@generic:~ # 
Install packages
root@generic:~ # pkg install -y python3 sudo 


The ansible user will become a member of the wheel group on my network. Use visudo to grant execute permissions to execute commands as root.

root@generic:~ # visudo

I still use passwords for sudo even for the ansible user.

## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL) ALL

Configure the network

Network interfaces

I use two network interface on my firewall. I use the a usb network adapter for the second interface. Please note the internal network interface on the

The network interfaces are - like all system settings configured in /etc/rc.conf on FreeBSD. User sysrc to configure them.

raspberry-pi is also connected over USB.

  • ue0 is connected to my internet router. I use a fixed IP address.
  • ue1 is my internal interface.
root@pifire001:~ # sysrc ifconfig_ue0="inet netmask"
ifconfig_ue0: inet netmask -> inet netmask
root@pifire001:~ # sysrc ifconfig_ue1="inet 192.168.yyy.yyy netmask"
ifconfig_ue1: inet 192.168.yyy.1 netmask -> inet 192.168.yyy.1 netmask

There is ifconfig_DEFAULT variable set in the /etc/rc.conf. Remove it with sysrc.

root@pifire001:~ # sysrc ifconfig_DEFAULT=""

Set the default route

Set the default route to your internet connection.

defaultrouter: NO -> 192.168.yyy.1
root@pifire001:/etc # 

restart the network services

Restart netif service to make the network connection active.

root@pifire001:~ # /etc/rc.d/netif restart

Restart the routing to get the default route configured.

root@pifire001:~ # /etc/rc.d/routing restart
delete host gateway lo0
route: route has not been found
delete net default: gateway fib 0: not in table
delete host ::1: gateway lo0
delete net fe80::: gateway ::1
delete net ff02::: gateway ::1
delete net ::ffff: gateway ::1
delete net :: gateway ::1
add host gateway lo0
add net default: gateway
add host ::1: gateway lo0
add net fe80::: gateway ::1
add net ff02::: gateway ::1
add net ::ffff: gateway ::1
add net :: gateway ::1
root@pifire001:~ # 

Enable routing

FreeBSD doesn’t route the network by default, the net.inet.ip.forwarding system variable needs to be set. This can be enabled with the gateway_enable variable in /etc/rc.conf, execute sysrc gateway_enable="YES"` to set it.

root@pifire001:~ # sysrc gateway_enable="YES" 
gateway_enable: NO -> YES

To enable routing set the net.inet.ip.forwarding variable to 1 with sysctl.

root@pifire001:~ # sysctl net.inet.ip.forwarding
net.inet.ip.forwarding: 0
root@pifire001:~ # sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 0 -> 1
root@pifire001:~ # sysctl net.inet.ip.forwarding 
net.inet.ip.forwarding: 1
root@pifire001:~ # 


I use pf as my firewall.

Enable pf & logging

To enable pf when the system starts up we need to pf_enable=yes and pflog_enable=yes in /etc/rc.conf. pflog is the logging daemon for the pf firewall.

root@pifire001:~ # sysrc pf_enable=yes
pf_enable: NO -> yes
root@pifire001:~ #  sysrc pflog_enable=yes
pflog_enable: NO -> yes
root@pifire001:~ # 

And start the pflog daemon.

root@pifire001:~ # /etc/rc.d/pflog start
Starting pflog.

firewall rules

Create /etc/pf.conf with your firewall rules. You’ll find the firewall rule I use below.

Some remark on my firewall rules:

  • It’s possible to set a network variable automatically with localnet=$int_if:network but if the interface isn’t configured when pf is started during the setup startup. The firewall rules will not be loaded.
# set external / internal interface

# set network ranges
# localnet=$int_if:network
localnet="{ }"
firewall_external_ips="{ 192.168.yyy.4}"
none_routeable="{,,, \,,, \, }"

firewall_tcp_services = "{ }"
firewall_udp_services = "{ 53, 67, 68 }"

# default block policy, allow lo traffic
set block-policy drop
set skip on lo

# Nat rules need to be defined first
nat on $ext_if from $localnet to any -> ($ext_if)

# block everything by default 
block in log all
block out log all

# dont block the mgmt systems ( - for now -)
pass in quick from $mgmt_systems to any 
pass out quick from $mgmt_systems to any 
pass in quick from $firewall_internal_ips to any 
pass out quick from $firewall_internal_ips to any 
pass out quick from $firewall_external_ips to any 

# allow firewall services
block in quick on $ext_if
block in log quick to $firewall_external_ips
pass in quick on $int_if proto udp to $firewall_internal_ips port $firewall_udp_services
pass in quick on $int_if inet proto icmp icmp-type echoreq
block in log quick to $firewall_internal_ips

# block all access to private_addreses 
block in quick log from any to $private_addresses
block out quick log from any to $private_addresses

# block all non-routeable traffic on the external interface 
block in log quick on $ext_if from $none_routeable to any
block out log quick on $ext_if from any to $none_routeable

# allow outgoing (all allowed - for now - )
pass out quick all keep state

pass in quick on $int_if inet all keep state

# allow localnet traffic
pass from { lo0, $localnet } to any keep state

# allow ping
pass inet proto icmp icmp-type echoreq

Start the pf service

To active the firewall you need to start the pf service.

root@pifire001:~ # /etc/rc.d/pf start


To display the firewall rules:

  • pfctl -s rules will display the firewall rules
  • pfctl -s nat will display the nat rules.
  • ```pfctl -s states will display the current stateful table.

Firewall rules with logging enabled can be analyzed by monitoring the pflog0 interface with tcpdump.

root@pifire001:~/scripts # tcpdump -n -e -ttt -i pflog0


I’ll use unbound as my dns server, unbound is a nice dns server/resolver and support dns over tls by default.

install unbound

root@pifire001:/etc # pkg install -y unbound ca_root_nss

Enable sysrc

root@pifire001:/etc # sysrc unbound_enable="YES"

first start

root@pifire001:/etc # /usr/local/etc/rc.d/unbound start


# cd /usr/local/etc/unbound/

You’ll need set the interface where unbound will run on.

# vi unbound.conf
        # interface: 2001:DB8::5


Allow your localnetwork to query the dns server.

        # access-control: ::ffff: allow

        access-control: allow

Enable dns-over-tls, I’ll use quad9 as my dns provider with dns-over-tls.

#       forward-host:

  name: "."
  forward-ssl-upstream: yes

Restart ubound to active the settings.

# /usr/local/etc/rc.d/unbound restart
Stopping unbound.
Waiting for PIDS: 61238.
Obtaining a trust anchor...
Starting unbound.


install isc-dhcpd

root@pifire001:~ # pkg install -y isc-dhcp44-server
Updating FreeBSD repository catalogue...
FreeBSD repository is up to date.
All repositories are up to date.
The following 1 package(s) will be affected (of 0 checked):

New packages to be INSTALLED:
	isc-dhcp44-server: 4.4.2_1

Number of packages to be installed: 1

The process will require 6 MiB more space.
1 MiB to be downloaded.
[1/1] Fetching isc-dhcp44-server-4.4.2_1.txz:  24%  312 KiB 319.5kB/s    00:03 E[1/1] Fetching isc-dhcp44-server-4.4.2_1.txz:  87%    1 MiB 835.6kB/s    00:00 E[1/1] Fetching isc-dhcp44-server-4.4.2_1.txz: 100%    1 MiB 659.6kB/s    00:02    
Checking integrity... done (0 conflicting)
[1/1] Installing isc-dhcp44-server-4.4.2_1...
===> Creating groups.
Creating group 'dhcpd' with gid '136'.
===> Creating users
Creating user 'dhcpd' with uid '136'.
[1/1] Extracting isc-dhcp44-server-4.4.2_1: 100%
Message from isc-dhcp44-server-4.4.2_1:

****  To setup dhcpd, please edit /usr/local/etc/dhcpd.conf.

****  This port installs the dhcp daemon, but doesn't invoke dhcpd by default.
      If you want to invoke dhcpd at startup, add these lines to /etc/rc.conf:

	    dhcpd_enable="YES"				# dhcpd enabled?
	    dhcpd_flags="-q"				# command option(s)
	    dhcpd_conf="/usr/local/etc/dhcpd.conf"	# configuration file
	    dhcpd_ifaces=""				# ethernet interface(s)
	    dhcpd_withumask="022"			# file creation mask

****  If compiled with paranoia support (the default), the following rc.conf
      options are also supported:

	    dhcpd_chuser_enable="YES"		# runs w/o privileges?
	    dhcpd_withuser="dhcpd"		# user name to run as
	    dhcpd_withgroup="dhcpd"		# group name to run as
	    dhcpd_chroot_enable="YES"		# runs chrooted?
	    dhcpd_devfs_enable="YES"		# use devfs if available?
	    dhcpd_rootdir="/var/db/dhcpd"	# directory to run in
	    dhcpd_includedir="<some_dir>"	# directory with config-
						  files to include

****  WARNING: never edit the chrooted or jailed dhcpd.conf file but
      /usr/local/etc/dhcpd.conf instead which is always copied where
      needed upon startup.
root@pifire001:~ # 

update rc.conf

dhcpd_chuser_enable="YES"		# runs w/o privileges?
dhcpd_withuser="dhcpd"		# user name to run as
dhcpd_withgroup="dhcpd"		# group name to run as
dhcpd_chroot_enable="YES"		# runs chrooted?
dhcpd_devfs_enable="YES"		# use devfs if available?
dhcpd_rootdir="/var/db/dhcpd"	# directory to run in


Edit /usr/local/etc/dhcpd.conf and update it for your network.


option domain-name "intern.stafnet.local";
option domain-name-servers,,xxx,,xxx;


subnet netmask {
  option routers;

fixed ip addresses

host snuffel {
   hardware ethernet XX:XX:XX:XX:XX:XX;


root@pifire001:/usr/local/etc # /usr/local/etc/rc.d/isc-dhcpd start

Have fun!


Leave a comment