OPNsense
OPNsense is an open-source firewall and routing platform. In my homelab it runs as a virtual machine on Proxmox.
About the examples. The addresses, hostnames, and keys in these guides are examples. Replace them with values for your own network. Menu paths match OPNsense 26.7.
- Type
- Virtual machine on Proxmox VE
- Software
- OPNsense 26.7
- VM ID
- 100
- Virtual CPUs
- 4, on one socket
- Memory
- 2 GiB, fixed (ballooning off)
- Disk
- 32 GiB on
local-lvm - Network
- Two VirtIO interfaces: LAN on
vmbr0, WAN onvmbr1 - Start at boot
- Yes
- Guides
- Static IP mapping, WireGuard VPN, Unbound ad blocking, Caddy reverse proxy
Creating the virtual machine in Proxmox.
- Get the installer. Download the OPNsense amd64 DVD image from opnsense.org, unpack it, and upload the ISO under
local > ISO Imagesin Proxmox. - Start the wizard. Click Create VM. Give it ID
100and the nameopnsense, and tick Start at boot so the firewall returns on its own after a power cut. - Choose the ISO. On the OS tab select the uploaded image and set the guest type to Other.
- System. Keep the default firmware and machine type, and set the SCSI controller to VirtIO SCSI single.
- Disk. Create a 32 GiB disk on
local-lvmon the SCSI bus, and tick Discard and IO thread. This VM uses the Write back cache mode, which is faster but can lose the most recent writes if power fails suddenly. - CPU. One socket with 4 cores and the type
x86-64-v2-AES. - Memory.
2048MiB, with Ballooning turned off so the firewall’s memory never shrinks. - Network. Give the first card the VirtIO model on
vmbr0and set multiqueue to8. After the wizard finishes, add a second network device underHardware > Add > Network Device, again VirtIO with multiqueue8, onvmbr1.
Installing OPNsense onto the disk.
- Boot the installer. Start the VM and open its console. At the login prompt sign in as
installerwith the passwordopnsense. - Run the guided install. Choose a keymap, pick the guided installation, and select the 32 GiB virtual disk as the target.
- Set the root password. Choose a strong one when asked, then let the installer finish and reboot.
- Remove the ISO. In Proxmox open
Hardware > CD/DVD Driveand choose Do not use any media, so the VM boots from its disk. - Assign the interfaces. From the console menu choose Assign interfaces. The first virtual card,
vtnet0, becomes LAN and the second,vtnet1, becomes WAN. - Set the LAN address. Use the console menu’s option for setting an interface IP address, then open the web interface from a device on the LAN and work through the setup wizard.
How the virtual cards map to the network.
| Interface | Device | Proxmox bridge | Role |
|---|---|---|---|
| LAN | vtnet0 | vmbr0 | The home network |
| WAN | vtnet1 | vmbr1 | The uplink to the internet |
| WireGuard | wg0 | none (virtual) | VPN clients, from Guide 2 |
Because the network cards are virtual, hardware offloading is turned off. Under Interfaces > Settings, hardware checksum offload, TCP segmentation offload, and large receive offload are all disabled, which avoids dropped or corrupted traffic on VirtIO interfaces. The assignments themselves live under Interfaces > Assignments.
How hard it works.
| Measure | Average | Highest reading |
|---|---|---|
| Processor | 3.5% of 4 vCPUs | 5.5% |
| Memory | 1.30 GiB of 2 GiB (65%) | 1.31 GiB |
| Network | about 0.6 MiB/s in and 0.6 MiB/s out |
Proxmox reports these figures from the host’s point of view because the QEMU guest agent is not installed, so memory includes the guest’s cache and should be read as an upper bound. Each reading is an average over roughly three hours, so short spikes do not show. They were taken over the eight days to 19 September 2026.
- Processor. The load is very low, so the four virtual CPUs are more than the firewall needs. Two would probably do, and that would leave more scheduling room on a four-core host shared by three guests.
- Memory. About a third of the 2 GiB is free, which is comfortable for the current set of services.
- Guest agent. Installing the QEMU guest agent in the guest and turning on QEMU Guest Agent under the VM’s
Optionsgives Proxmox accurate memory figures, clean shutdowns, and consistent snapshots. It is not enabled on this VM yet.
Static IP mapping
Give a device the same address every time.
A static mapping tells the DHCP server to always hand one device the same IP address, matched by its MAC address. The device keeps using DHCP, so nothing changes on the client, but its address stops moving. That suits servers, printers, and cameras, and it keeps every reserved address in one place. On this install the DHCP server is the ISC DHCPv4 plugin, which the menu labels [legacy], so the steps use its screens.
- Find the device’s MAC address. Open
Services > ISC DHCPv4 [legacy] > Leases. Each current lease lists an address, a MAC address, and a hostname, so a device that is already online is easy to spot. You can also read the MAC address from the device’s own network settings. - Choose an address outside the dynamic pool. Open
Services > ISC DHCPv4 [legacy] > [LAN]and note the range near the top of the page. Pick an unused address in the same subnet that falls outside that range. If the pool runs from .100 to .199, for example, keep .20 to .99 for static mappings. - Add the mapping. Scroll to the static mappings table at the bottom of the same page and click the add (+) button.
- Enter the device details. Fill in the MAC address, the IP address, a hostname, and a description, as in the example below, and save.
- Apply the change. Use the Apply changes button on the page so the DHCP service reloads with the new mapping.
- Renew the client’s lease. Reconnect the device to the network, or renew its lease from the operating system. It then receives the reserved address.
Example values
| MAC address | 02:00:00:aa:bb:cc |
|---|---|
| IP address | 192.168.10.20 |
| Hostname | nas |
| Description | Home NAS |
Verify
The device should now appear on the Leases page with the reserved address. From the client and from another device on the LAN:
$ ip -4 addr show # on the client: expect 192.168.10.20
$ ping -c 2 192.168.10.20 # from another LAN device
Notes
- To reach the device by name, enable Register DHCP static mappings in
Services > Unbound DNS > General. Unbound then answers for the hostname you entered. - Reserve addresses for anything that other services point at, such as the upstream servers behind the Caddy reverse proxy in Guide 4.
- Mappings belong to one interface. A device that moves to a different network or VLAN needs a mapping there too.
WireGuard VPN
Reach the home network from anywhere.
WireGuard is a fast, modern VPN that is built into OPNsense. The setup has one instance, which acts as the server, and one peer for each device that connects. The example below lets a phone reach the home network from outside.
- Create the instance. Open
VPN > WireGuard > Instancesand add one. Give it a name, generate a key pair with the gear icon, set the listen port to51820, and give it a tunnel address in its own subnet, such as10.10.10.1/24. Save it. - Make sure WireGuard is enabled. Turn on WireGuard itself on the same page and click Apply. The instance does not start until you do.
- Create a peer with the peer generator. Open
VPN > WireGuard > Peer generator. Choose the instance, enter the public endpoint that clients will connect to, name the peer, give it a tunnel address, choose which networks should travel through the tunnel, and set a DNS server. The page builds a complete client configuration and a QR code. Store the peer on the server when you are done. - Open the port on the WAN. In
Firewall > Rules > WAN, add a pass rule for protocol UDP, with the WAN address as the destination and destination port51820. If OPNsense sits behind another router, forward UDP 51820 to it there as well. - Allow traffic from the tunnel. In
Firewall > Rules > WireGuard (Group), add a pass rule whose source is the VPN subnet (10.10.10.0/24) and whose destination is the LAN network. Rules on this group cover every WireGuard instance. - Import the configuration on the device. In the WireGuard app on the phone, scan the QR code or import the configuration, then turn the tunnel on.
Example values
| Instance name | wg-home |
|---|---|
| Listen port | 51820 |
| Instance tunnel address | 10.10.10.1/24 |
| Peer name | phone |
| Peer tunnel address | 10.10.10.2/32 |
| Endpoint | vpn.example.com:51820 |
The generated client configuration looks like this, with placeholders in place of real keys:
[Interface]
PrivateKey = <client-private-key>
Address = 10.10.10.2/32
DNS = 192.168.10.1
[Peer]
PublicKey = <server-public-key>
Endpoint = vpn.example.com:51820
AllowedIPs = 192.168.10.0/24, 10.10.10.0/24
PersistentKeepalive = 25
Verify
Open VPN > WireGuard > Status and check that the peer shows a recent latest handshake. Then turn Wi-Fi off on the phone, connect over cellular, and open an internal address. On the firewall’s shell, wg show lists the same peers and handshakes.
Notes
- The QR code contains the client’s private key. Treat it like a password: do not publish it, and do not send it over an unencrypted channel.
- Split or full tunnel. Listing only the LAN and VPN subnets in
AllowedIPssends home traffic through the tunnel and nothing else. Using0.0.0.0/0sends everything through home, and then the VPN subnet also needs an outbound NAT rule inFirewall > NAT > Source NATif automatic rules do not already cover it. - Setting the client’s DNS to the firewall’s LAN address gives VPN devices the ad blocking from Guide 3. Unbound must also accept queries from the VPN subnet, which you allow in
Services > Unbound DNS > Access Lists.
Unbound DNS ad blocking
Block ads and trackers for the whole network.
Unbound is the DNS resolver built into OPNsense. Its blocklist feature refuses to resolve domains used for ads, trackers, and malware, so every device that uses the firewall for DNS is filtered without installing anything on it. In this lab I use the HaGeZi Multi NORMAL list, a balanced choice.
- Check that Unbound is your resolver. Open
Services > Unbound DNS > General. Confirm that it is enabled and listening on the interfaces your clients use, and that DHCP hands out the firewall’s address as the DNS server, which is the default. - Add a blocklist. Open
Services > Unbound DNS > Blocklistsand add an entry. - Pick a list. Under Type, choose a collection. The menu offers the Steven Black, AdGuard, EasyList, and EasyPrivacy lists, and the HaGeZi tiers LIGHT, NORMAL, PRO, PRO++, and ULTIMATE. Start with NORMAL - Relaxed/Balanced Blocking: the stricter tiers block more but also break more sites. Give the entry a description.
- Allow what breaks. If a list blocks a domain you need, add it to the entry’s allowlist rather than switching the whole list off.
- Save and apply. Unbound restarts and downloads the list.
- Close the common bypasses. Some devices ignore your DNS server and use their own. In
Firewall > NAT > Destination NAT, add a rule on the LAN that redirects TCP and UDP port 53 from every destination except the firewall to the firewall itself. Also block outbound port 853 (DNS over TLS). Choosing the DoH/VPN/TOR/Proxy Bypass list as a second entry helps against DNS over HTTPS.
Example values
| Enabled | Checked |
|---|---|
| Type | NORMAL - Relaxed/Balanced Blocking |
| Description | HaGeZi Multi NORMAL |
| Allowlist | example-cdn.net (only if it breaks something) |
| Return NXDOMAIN | Unchecked, so blocked names answer 0.0.0.0 |
Verify
Ask the firewall for a well-known advertising domain and for an ordinary one. A blocked name answers 0.0.0.0, and an allowed name resolves normally.
$ dig +short doubleclick.net @192.168.10.1 # blocked: 0.0.0.0
$ dig +short example.com @192.168.10.1 # allowed: a normal address
Services > Unbound DNS > Statistics shows query counts, and it is the quickest way to see whether clients are using the resolver at all.
Notes
- If a site or app stops working after a change, test the domain with
digfirst. If it answers0.0.0.0, add it to the allowlist. - The blocklist runs inside Unbound, so it also applies to VPN clients that use the firewall for DNS (Guide 2).
- Blocklists reduce ads and tracking. They are not a security boundary, and a determined app can bypass DNS altogether.
Caddy reverse proxy
One front door for web services.
A reverse proxy is a single entry point for web services. Clients connect over HTTPS using a hostname, and the proxy forwards each request to the right service on the internal network. Caddy, installed on OPNsense as the os-caddy plugin, also obtains and renews Let’s Encrypt certificates automatically.
- Install the plugin. Open
System > Firmware > Plugins, installos-caddy, and reload the web interface. - Free ports 80 and 443. The OPNsense web interface listens on 443 by default, and Caddy needs that port. Open
System > Settings > Administration, set the interface’s TCP port to something else, such as8443, and reconnect on the new port. Do this from a device on the LAN, and confirm you can still sign in before you continue. - Allow web traffic. In
Firewall > Rules > WAN, add pass rules for TCP ports 80 and 443 to this firewall. Port 80 is needed for the certificate challenge unless you use the DNS challenge in step 5. - Enable Caddy. Open
Services > Caddy > General Settings, tick Enable, enter an email address for certificate notices, and leave Auto HTTPS on its default. Save and apply. - Add a domain. Open
Services > Caddy > Reverse Proxyand add an entry on the Domains tab forapp.example.com. For names used only inside your network, use the DNS challenge, which needs a DNS provider configured in General Settings, so that no inbound port is required. - Add a handler. On the Handlers tab, choose the domain, then enter the upstream address and port of the service. Enable TLS only if the upstream itself speaks HTTPS, and skip certificate verification only if it uses a self-signed certificate. Save and apply.
- Point DNS at Caddy. Publicly, create an A record for
app.example.comthat points to your public IP address. Inside the network, add an override inServices > Unbound DNS > Overridesthat maps the same name to the firewall’s LAN address, so that local clients reach Caddy directly (often called split DNS).
Example values
| Domain | app.example.com |
|---|---|
| Upstream address | 192.168.10.20 (the reserved address from Guide 1) |
| Upstream port | 8080 |
| Upstream TLS | Off, because the service speaks plain HTTP |
| ACME email | you@example.com |
Verify
$ curl -I https://app.example.com
# expect a normal HTTP response and a valid certificate
If the request fails, Services > Caddy > Log File shows certificate and proxy errors, and it is usually the fastest way to find the cause.
Notes
- Expose only what you need. The Reverse Proxy page also supports access lists to limit which clients may connect and basic authentication for an extra layer. Administration tools are better reached over WireGuard (Guide 2) than through a public hostname.
- Give every upstream server a static mapping (Guide 1). A handler that points at an address that later changes stops working without any warning.
- The reverse proxy handles HTTP and HTTPS. Other kinds of traffic need a different approach.
How the four fit.
- Static mappings give the servers behind Caddy addresses that never change.
- Unbound overrides give Caddy hostnames a direct route on the LAN, and its blocklist filters every device, including VPN clients.
- WireGuard is the safe way in for anything you would rather not put behind a public hostname.
The rest of the lab.
- ProxmoxThe hypervisor that hosts this virtual machine.
- Docker HostA virtual machine for running containers.
- Home Assistant OSThe appliance operating system for Home Assistant.