OPNsense - zbrewer/homelab GitHub Wiki
I use a virtualized OPNsense instance as my primary router/firewall. Virtualization allows me to utilize the hardware as more than just a firewall; for example, I can run a DNS server and UniFi controller on it as well. In addition, this allows me to take snapshots of the OPNsense VM for easy restore-ability if something goes wrong when changing configurations. That being said, there are downsides to this approach well documented on the internet such as taking down the entire network (router) whenever the host needs to reboot.
Specifically, I am running OPNsense on a Project TinyMiniMicro node: a Lenovo M920q. Inside I have 16GB of RAM, an Intel i7-8700T processor (6c, 12t), and a 512 GB m.2 SSD. This is not only a physically compact machine but its power draw is relatively low and, under normal circumstances, it is relatively quiet.
In addition to the basic components, I chose the M920q (or the M720q) due to the fact that it can support up to a PCIe 3.0 x8 slot. This requires a proprietary riser (P/N 01AJ940) but it is readily available on eBay. Details are given on the STH forums here. This allowed me to install a Mellanox ConnectX-3 dual 10G SFP+ NIC and use one port for the LAN connection and one for WAN. Specifically, I went with an MCX312B (the pro model) but a MCX312A or any of the vendor-branded versions of either would also work fine. These cards both require custom baffles to secure to the back of the chassis but STL files for 3D printed parts are available on Thingiverse (MCX312A, MCX312B).
The Mellanox (and other) 10G NICs do tend to run a bit hot so following the instructions for temperature monitoring on the Telegraf page is recommended. That being said, the maximum junction temperature for these NICs is 105dC so as long as they are operating below that they should be fine. Do note too that copper transceivers will generate more heat than DACs or fiber.
Finally, I use OPNsense to segment my network into multiple VLANs so the LAN interface on the Mellanox card must be VLAN aware in Proxmox. This requires a bit of extra configuration to make sure that the correct VLAN IDs are available. See the instructions on the Proxmox page for more details.
Numerous guide such as this, this, or this exist for installing OPNsense in a VM. In order to avoid rehashing the instructions, the basic steps are this:
- Create virtual (bridge) interfaces for the LAN and WAN ports, tying them to the correct physical adapters.
- Set the LAN bridge interface to VLAN aware per the instructions on the Proxmox page.
- Download the ISO file from the OPNsense website and upload it to Proxmox, as normal.
- Create a VM for OPNsense. Give it at least 8GB of storage (ideally more like 32GB), 4 CPUs (all available if the machine is primarily a router/firewall), set the CPU type to
host
, 8GB of RAM, and select one of the bridge interfaces (set the model toVirtIO
). - After creating the VM, go to its hardware page and select
add
to add the other (WAN or LAN) interface in the same way. - Start the machine and follow the installation prompts.
ZFS
can be selected as the filesystem type andStripe
for its configuration.
After installation, connect to the LAN port and visit 192.168.1.1
to access the web UI. Login with the username root
and the password opnsense
(this should be changed after initial login).
OPNsense includes an Unbound DNS server that is enabled by default. That being said, a bit of extra configuration is required.
First, go to Services > Unbound DNS > General
and ensure that the Enable
, DNSSEC
, DHCP Registration
, and DHCP Static Mappings
options are all enabled.
From there, go to Services > Unbound DNS > DNS over TLS
to set up Cloudflare as the upstream DNS provider and to connect over TLS. Add 4 servers, each with the hostname cloudflare-dns.com
and the port 853
. The IP addresses of the servers should be 1.1.1.1
, 1.0.0.1
, 2606:4700:4700::1111
, and 2606:4700:4700::1001
.
After all settings are saved and applied, setting the OPNsense machine as the DNS server from a client computer (port 53
) and making DNS queries should work, with the queries logged in OPNsense. In addition, visiting 1.1.1.1/help should show that the Cloudflare DNS servers are being used over TLS.
VLANs can be created by visiting Interfaces > Other Types > VLAN
and pressing the plus button. The VLAN name must meet some naming guidelines and it is easiest to include the VLAN tag in the name. For example, vlan0.100
for VLAN 100. Select the appropriate parent interface (usually LAN) and set the tag, priority, and description.
After saving and applying, go to Interfaces > Assignments
to create a new interface for the VLAN that was just created. The description should be a meaningful name for the VLAN, such as "DMZ" or "Guest". Save and apply again and you should be automatically taken to the Interfaces > <interface_name>
page. Here the Enable
check box should be ticked and Static IPv4
should be selected for IPv4 Configuration Type
. Finally, the IPv4 Address
under Static IPv4 configuration
should be set for the subnet desired. For example, setting it to 192.168.86.1
with the mask 24
will create a typical subnet with OPNsense available on 192.168.86.1
and the additional addresses from 192.168.86.2
to 192.168.86.254
available for use (192.168.86.255
is reserved for broadcast use).
At this point, the VLAN is set up and ready for use. Assigning the VLAN tag downstream (such as to another VM/CT on the host Proxmox machine, attached to the LAN bridge interface) and a static IP in the subnet should allow a link to be established. That being said, the firewall by default will not permit any traffic. To allow outbound connections to anything (and therefore general internet access), navigate to Firewall > Rules > <vlan_interface_name>
and create a new rule with *
as the source and destination. Save and apply.
Before setting up the WAN connection, it is important to realize that the web UI is available on all interfaces by default. This means that, once the WAN connection is set up, the configuration page could be accessed via your public IP address. This could be stopped with a firewall rule or, under System > Settings > Administration
the Listen Interfaces
option can be changed so that it just listens on one of the LAN interfaces (such as the interface for a management VLAN).
CenturyLink fiber internet can be configured by connecting the WAN port directly to the ISP provided ONT. In order to do this, some extra steps are required. These are the steps necessary in my area to set up a PPPoE connection but details in other areas may vary.
CenturyLink connects over a VLAN, 201 in my case, so a new VLAN must be created per the steps above with this tag. The interface it is assigned to should be WAN
. Then, on the Interfaces > WAN
page, the IPv4 Configuration Type
should be set to PPPoE
and the username and password provided by CenturyLink should be entered under PPPoE configuration
(note that the username should include the @centurylink.net
suffix).
At this point, a reboot may be required but an IP address should be pulled by the WAN interface and populated on the Lobby > Dashboard
page (under Interfaces
).
Because PPPoE is single-threaded on BSD (which OPNsense is built on), WAN performance is directly tied to single-threaded performance of the host's CPU. On a modern x86 machine (like the i7-8700T I'm using) the performance isn't a problem and getting 1G down/up should be doable with no further tweaks. That being said, some options do exist if necessary to try to improve performance. These tweaks are documented across the internet with a good reference source here; however, the basics are:
- Set
Multiqueue
to8
under the Proxmox advances settings for the WAN bridge interface. - Make sure all hardware offloading is disabled in OPNsense.
- Add three tunables under
System > Settings > Tunables
:-
net.isr.bindthreads
with a value of1
. -
net.isr.maxthreads
with a value of-1
. -
net.isr.dispatch
with a value ofdeferred
.
-
For any interfaces where its use is desired, the DHCP server can be configured under Services > DHCPv4 > <interface_name>
. Check the enable
box and set the DHCP range for the address range that should be assigned automatically. Then specify the DHCP server (if desired) and add any static mappings.
By default, the OPNsense firewall doesn't include any firewall rules for a new interface meaning that all traffic is denied. Rules must be configured to permit the desired traffic while keeping in mind that they are generally evaluated top to bottom and the first rule that matches is applied. This means that, if a rule that allows the traffic is followed by one that would block it, the traffic is allowed.
In general, I typically add five "default" rules to interfaces that are supposed to have limited access (no or restricted access across VLANs). These are all in
rules and are (in top-to-bottom order):
- Allow access specifically to the DNS server(s). This is necessary if the DNS server is on a different VLAN (for example, a Pi-Hole server) or if it is the firewall itself. The action for this rule is
Pass
, the source/port is*
(any), the destination is the DNS server's IP (setting up an alias for this is nice), and the destination port is53
. - Prevent other access to the firewall (especially if the management application is set to listen on the interface but it shouldn't be accessed). The rule here is
Block
orReject
and the destination isThis Firewall
. - Allow (L3) access to other devices on the same VLAN. This uses
<interface> net
as both the source and destination and thePass
action. - Prevent access to other VLANs. I do this by creating an alias for
InternalNetworks
that references all private IP ranges. Its type isNetwork(s)
and its content is10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
. I then create a rule on the interface of the VLAN I want to restrict and set the action toBlock
orReject
, the protocol/source/ports to*
(any), and the destination to theInternalNetworks
alias. This prevents any L3 access to other VLANs (and L2 traffic is already segmented by the VLANs themselves). - Allow access to all other destinations (namely, those that aren't internal). This rule is at the very bottom of the list and uses
*
(any) for the protocol, source, destination, and ports.
At this point, access to other VLANs is not allowed except for the DNS server. Further holes can be punched through the firewall for access to devices on other VLANs (such as a reverse proxy) by creating Pass
rules before the rule that blocks access to other VLANs. Internet access can be further restricted by adding more Block
or Reject
rules before the final rule that allows all remaining connections.
If segmenting Users/Guest from IoT, some additional rules may be needed for devices such as Chromecasts to work.
To start, many of these devices rely on multicast for discovery. Since multicast is not propagated across VLANs, the Multicast DNS Proxy must be installed per the OPNsense docs and configured for the IoT and Users/Guests VLAN(s) (as appropriate).
In addition, the following firewall rules must be added to the client (Users and/or Guest) VLAN(s). Note that firewall aliases are extremely helpful when setting this up.
Service | Protocol | Source | Source Port | Destination | Destination Port |
---|---|---|---|---|---|
Chromecast | TCP | * | * | 8008, 8009, 8443 | |
Chromecast | UDP | * | * | 32768:61000 | |
Plex | TCP | * | * | <plex_servers> | 32400 |
Reverse Proxy | TCP | * | * | <reverse_proxy> | 443 |
Tablo | TCP | * | * | 80, 8887 | |
Samba | TCP | * | * | <samba_server(s)> | 445 |
Steam Link | TCP | <steam_link_hosts> | * | <steam_link_clients> | 27036, 27037 |
Steam Link | UDP | <steam_link_hosts> | * | <steam_link_clients> | 27031, 27036 |
And the following rules must be added to the IoT VLAN:
Service | Protocol | Source | Source Port | Destination | Destination Port |
---|---|---|---|---|---|
Plex | TCP | <plex_players> | * | <plex_servers> | 32400 |
Steam Link | TCP | <steam_link_clients> | * | <steam_link_hosts> | 27036, 27037 |
Steam Link | UDP | <steam_link_clients> | * | <steam_link_hosts> | 27031, 27036 |
OPNsense can also be set up as a DDNS server when using a supported DNS server for your domain. I have this set up using Cloudflare. The DDNS plugin can be installed in OPNsense per the instructions here and then it can be configured as follows:
Got to Services > Dynamic DNS > Settings
and click on the plus button. Make sure Enabled
is ticked and set the Service
to Cloudflare
. Set the zone
to the base URL (such as example.com
) and add the hostname(s) you would like to update to the Hostname(s)
field (such as vpn.example.com
). Set the Check ip method
to Interface
and set the Interface
to WAN
.
Finally, create an API Token for specific use by the DDNS server on the Cloudflare website and give it DNS:edit
permissions for the zone (base URL) of the site you want to update with DDNS. While you're there, create A records for the hostname(s) the DDNS server will try to update (such as vpn.example.com
). These can point to a dummy IP address or now such as 8.8.8.8
(Google's DNS). Make sure to de-select the Proxy
option, otherwise the IP address returned by public nameservers will be a Cloudflare IP and not the IP set by DDNS.
Copy the Cloudflare API token into the password
field in OPNsense and leave the username
blank. Save and apply. After a second, the A record(s) should be updated with your public IP.
OPNsense has the ability to shape traffic, such as limiting the bandwidth available to some clients. This is useful to, for example, restrict speeds on the guest network to ensure that there is plenty of bandwidth available for servers and privileged users. A comprehensive guide is available here detailing how to set this up; however, below will be a description of how to set up the traffic shaper to limit the guest network to 100Mbps down and 50Mbps up.
First, navigate to the Firewall > Shaper > Pipes
page in the OPNsense UI. On the Pipes
tab, create one upload pipe and one download pipe. Specify the bandwidth (and unit) for each, make sure they have a good description (for example, 100Mbps_guest_download
), and make sure they are enabled.
Now, navigate to the Rules
tab and create a new rule for the download limit. Set the Interface
to WAN
, the Protocol
to IP
, the Source
to any
, the Src-port
to any
, and the Destination
to the guest subnet in CIDR notation (for example, 10.0.0.0/24
). Set the Dst-port
to any
and specify the download pipe created in the previous step for the target. Make sure there is a good description and that it is enabled before saving and applying.
An upload pipe can be created in the same way except for the fact that its Target
will be the upload pipe and its Source
and Destination
will be flipped (in other words, the Source
will be the subnet and the Destination
will be any
). Save and apply this pipe as well.
It is important to specify the WAN
interface for these rules since they will then control the bandwidth between the WAN (internet) and the guest network (subnet) while not limiting any local throughput.
OPNsense itself can run a Wireguard server to allow remote connections. The official OPNsense documentation does a good job of walking through this process so I just want to add a few notes here.
The first point worth noting is that the new Wireguard setup will get its own interface in OPNsense. This is a "virtual" interface since its traffic will be tunneled and travel over some other physical interface (typically the WAN). This means that L2 concepts, such as VLANS, don't apply here. If you'd like to configure it to have the same access as the other VPN interface/subnet (which may have its own VLAN), you can create a new group under Firewall > Groups
and put both interfaces there. Then move all firewall rules to this group. This will ensure that they have the same access. That being said, additional rules can still be added to each interface independently, if desired.
The next point worth noting is that the new Wireguard interface will need its own address space (subnet) that is independent from any that have already been created. The easiest way to do this is to use an entirely new class C block (/24 aka a subnet mask of 255.255.255.0); however, it could also be done by splitting a class C block and using part for the VPN VLAN/other VPN hosts and part for the Wireguard Road Warrior interface. This could be useful if, for example, you'd like to keep the first two octets of your network addresses consistent and use the third octet to indicate that it is a VPN IP. This is just a matter of preference but see the cheat sheet here for examples of sub-class C blocks.
Finally, the last point worth noting is that reflection NAT is not a problem with this setup; however, you will need to ensure that the firewall is configured to allow connections to the firewall itself on the Wireguard port internally.