Moving From Sophos UTM 9 to pfSense

2020-09-01 8 min read Behind the scenes Network maintenance Networking Teknikal_Domain Unable to load comment count

Yeah I figure why not, at the same time that I’m replacing another key piece of network infrastructure , I might as well just replace the (second) most important piece, right? So cue the music, because…

Now, this is a story, all about how my life network got flipped, turned upside down, and I’d like to take a minute, just sit right there, I’ll tell you how I became the prince of a town called Bel Air the owner of a… just… just cut the music. Let’s begin.

So, the basics… I upgraded the box that sits between my network and the rest of the internet. (Technically, my network and the rest of the house, but…) In terms of core features, it’s the network router and gateway, and in terms of other features… it’s a firewall. The software package I was using was the UTM 9 (Unified Threat Management) by Sophos, and, if you’re familiar with that name, then you already know it does a lot.

Sophos UTM 9

Note: the successor to this is called Sophos XG, which… I’ve never touched, I just didn’t upgrade.

UTM 9, besides being a pretty standard firewall / NAT device, also did a lot more like application traffic shaping, what’s effectively Snort traffic monitoring, HTTP download virus scanning, URL blocking, email download scanning, email DKIM signing, email spam detection.. a lot of things. UTM could even do things like webserver protection, act as an AP controller, so much fun stuff.

It ran mainly off a system called confd, a central (to the device, in this case) configuration management engine, wrapped up in a nice (and slow) graphical web interface.

To the best of my knowledge, the real backend of the firewall / NAT section is iptables, which, let’s just say I’m glad it’s much simpler to use graphically.

A home use (non-commercial) version is available for free, licensed for 50 LAN-side IP addresses. This also comes with software and virus database updates.

And one final thing to note that will be important later: the UTM installer checks how many NICs (translated: Ethernet ports) your device has, and it will refuse to run unless there’s at least two, one for the WAN (internet / ISP / “everyone else”) side, and one for the LAN (own network) side.


pfSense is a network firewall application built on FreeBSD. It doesn’t have all the features of a full UTM, but in some ways it’s a little better. Besides being FOSS, it’s also based not on iptables for filtering and routing, but pf (packet filter) instead, hence the name, pfSense.

Instead of relying on one central configuration manager to keep all the services in check, pfSense makes edits directly, many of them requiring an explicit “save” or “apply” to take effect.

pfSense has a built in package manager to allow installing additional features that have been created by the community, like a .ovpn file exporter or a Zabbix monitoring agent, but by default you’ve still got quite a few things to play with: IPsec and OpenVPN VPNs, CARP for pfSense high availability, various methods of traffic shaping… and as a router, it’s got decent DHCP and DNS capabilities, with.. some quirks.

I’m obviously not going to give you a full, detailed walkthrough, but as a general comparison, the UI looks much cleaner, less cluttered, and feels more responsive, in my opinion, and it’s a very capable system indeed.


Whereas iptables is a core part of linux at this point, with a list of “chains” of rules that are evaluated top to bottom, and the first one to match within a chain will send the rule to any other specified chain, or specify a final action instead like ACCEPT or DROP, pf uses a rules file, like this:

pass   in  quick  on $IPsec                            inet            from any to any                             tracker 1597637882            keep state  label "VPN"
pass   in  quick  on $OpenVPN                          inet            from any to any                             tracker 1597540135            keep state  label "VPN"
block  in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to any          port 23            tracker 1597471764 flags S/SA             label "No telnet"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to port $Syncthing    tracker 1597548003 flags S/SA keep state  label "NAT Syncthing"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to $BorderProxy port $Jellyfin     tracker 1597548009 flags S/SA keep state  label "NAT Media server access"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to $BorderProxy port $Matrix       tracker 1597548015 flags S/SA keep state  label "NAT Allow Synapse to fedrerate with Matrix"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to $BorderProxy port $NewsTransfer tracker 1597548030 flags S/SA keep state  label "NAT Allow NNTP access to blog articles"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to $BorderProxy port $Web          tracker 1597548036 flags S/SA keep state  label "NAT Web access to services"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to port 22            tracker 1597548044 flags S/SA keep state  label "NAT Allow SSH for Git repos"
pass   in  quick  on $WAN reply-to ( re0 ) inet proto tcp  from any to port $SSHAlt       tracker 1597548050 flags S/SA keep state  label "NAT Allow SSH for other VCS repos"

If you read it left to right it actually just spells out what to do:

  • pass : Pass (allow)
  • in : Incoming traffic
  • on $WAN : On the WAN interface
  • reply to (re0 ) : (send all replied to NOT the original source)
  • inet : That’s IPv4 traffic, not IPv6
  • proto tcp : Using TCP
  • from any : Coming from anywhere
  • to $BorderProxy : Going to $BorderProxy (user alias,
  • port $Jellyfin : Destined for port $Jellyfin (user alias, 8096)
  • flags S/SA : Inspect the SYN and ACK flags, and only match traffic with the SYN flag set and the ACK flag unset
  • keep state : When matched, add a state entry to allow further communications through

While cool, it’s also a headache to understand. Like for example, notice how all the rules say quick? pf evaluates top to bottom, but the last rule that was matched will decide the outcome, not the first. The quick keyword causes rule evaluation to end right there, meaning that by putting quick on every rule, it’s effectively a first match wins again.

The user interface here automatically sets quick since it presents the rules table in a top-to-bottom, first match wins order.

This firewall just by itself is very powerful, with the ability to match many protocols (not just TCP / UDP), inspect arbitrary flag combinations, state tracking, and a rule can even, as a bonus action, set what processing queue to filter matched traffic into (for traffic shaping).

DHCP Quirks

Both UTM and pfSense offer DHCP to their LAN side, presented in a nice DHCP lease table. In Sophos UTM, from here you can press “Make static” and it’ll bring up the network host creation dialog with the host MAC, current IP, and hostname all pre-filled. You can assign any valid IP address to this, then save it. Next time it tries to renew it’s lease, it’ll be sent the static reservation data.

pfSense can do the same thing, except it only fills in the MAC address, you have to specify the IP, hostname (useful for (r)DNS), additional DNS servers (overrides global setting), and other pieces. You also *cannot assign a static reservation within the DHCP range. It just straight-up won’t let you. For example, if my DHCP range is set for, then adding a static for will just give me an error.

For the technical reasons, the DHCP service doesn’t take static reservations out of the DHCP pool, meaning that if it allows this to happen, it can actually double-book an address, giving the same IP to two different hosts, which is uh… kinda a big deal. With pfSense, you’ll want to set the DHCP range to be a sort of “guest” address range, and make reservations for permanent clients that reside outside of that range. For me, that means 100199 is the DHCP range, if a host is not in that range, I’ve made it static.

Two Interfaces, One NIC

And now for something that pfSense can do that UTM can’t: Run with one physical NIC. This is because the installer lets you configure the default LAN and WAN interfaces not just as a particular network card, but also a VLAN tag, if it needs one. I’ve already see this in practice, for example, default, untagged packets are the LAN side, and VLAN 10 tagged packets are the WAN side. It’ll take this and… just work, assuming the switch(es) that are handling the VLANs are handling it correctly. Meaning if you want to be budget, just about any device can become a network router and firewall if you have a VLAN-capable switch.

comments powered by Disqus