Building a Secure Home Network with OPNsense, WireGuard VPN [Mullvad], and VLAN Isolation

Published: at 01:30 PM

TL;DR

Complete guide to building a privacy-focused home network with OPNsense. All traffic routes through VPN with automatic failover - no DNS or IP leaks to your ISP.

Prerequisites:

  • Lenovo M920q (or similar) with Intel i350-T4 NIC
  • Managed switch for VLAN support
  • Basic networking knowledge
  • Mullvad VPN subscription

What you’ll set up:

  • Multi-VLAN network segmentation
  • Redundant WireGuard VPN tunnels with failover
  • AdGuard DNS filtering with kill switch
  • DMZ isolation on dedicated interface
  • Comprehensive firewall rules

Introduction

After years of using PFsense I finally got around to replacing it with OPNsense. While I’m at it, I also wanted to ‘improve/complicate’ the setup compared to what I had with PFsense.

Motivation was simple, get rid of PFsense and Netgear. Additionally, I wanted to improve security, privacy and maintainability while achieving my stated requirements. Most of these requirements had to be replicated from what I already had on PFsense using options available on OPNsense. (e.g AdGuard vs pfBlockerNG)

This guide serves as documentation for my future self while helping fellow HomeLabers, Techies and anyone who wants to get back some digital privacy. Hopefully this can help you replicate something similar for your own usecase.

Some specific parts, e.g testing or setting up VLANs on Network Switches and WiFi APs will be in separate posts. This post primarily focuses on OPNsense and requires additional configuration on network switches.

This blog uses Mullvad as VPN provider because there is no better alternative. The instructions here focus on Mullvad only but you can easily replace it with a VPN provider of your choice that supports wireguard.

Before we start, I want to do a shoutout to Schnerring who’s guide + OPNsense docs made migrating to OPNsense a breeze. His guide inspired me to make some decisions to my OPNsense setup that I did not have with PFsense before.

Without further BS, lets get into it.


Requirements

I had very specific goals, so please make necessary changes depending on your setup. Do no blindly copy what’s here.

Network Architecture Goals

Network Diagram showing the system architecture

VLAN Structure

The network is segmented into multiple VLANs, each serving a specific purpose with appropriate security controls:

VLANNamePurpose
LANMain NetworkSecure LAN network for “trusted” devices
VLAN10DMZDMZ for public-facing server running various services, apps and tools
VLAN40SecWiFiSecure WiFi network - extension to LAN but for wireless connections
VLAN42IoTSeparate VLAN for untrusted IoT devices
VLAN44GuestIsolated VLAN for all guest devices (untrusted)
VLAN46GoatNETSpecial purpose network (see below)
VLAN90PlaceholderCurrently not used
💡 Why GoatNET - VLAN46?

My wife works in marketing, tracking and analytics (AKA Dark side). She’s unable to do her job on LAN or VLAN40 (SecWiFi) due to anti-tracking and blocking measures that exist there. For her special case, I created dedicated VLAN46 which does not have any blocking in place. This network still gets routed through VPN, but she can work without my security measures getting in the way. In the end, happy wife = happy life.

Since I am using Mullvad DNS as upstream servers which come with filtering that VLAN46-GoatNET does not want. This requires a NAT rule that redirects DNS directly to 1.1.1.1 bypassing our AdGuard and Unbound. This is required for specific services that get filtered by Mullvad, for example google services such as ads.google.com or analytics.google.com.

If you choose to use 1.1.1.1 instead of Mullvad DNS e.g 100.64.0.1 as upstream servers for Unbound, then the NAT redirect would go to Unbound on port 5353 instead.

Core Requirements

Beyond the VLAN structure, the network must meet these essential requirements:

Connectivity & Routing:

  • Redundant VPN tunnel to the internet (primary with automatic failover to secondary)
  • IPv4 Only (IPv6 disabled)
  • All traffic routed through the 2 VPN tunnels with priority failover
  • Port forwarding for services such as Proxies, Cloud services, Plex, and any other self-hosted application
⚠️ Redundant VPN tunnel is essential

Sometimes VPN provider servers go down, for example maintenance, in such cases we want to retain access to internet.

Making your internet access dependent on a single tunnel will create problems, that’s why I do not recommend trying this setup with a single tunnel.

DNS & Security:

  • AdGuard DNS blocking for all networks (with VLAN46 exception)
  • Unbound Forwarding DNS Queries to Mullvad DNS servers
  • VLAN46 traffic routed through VPNs but without AdGuard
  • VLAN46 has no DNS filtering and goes directly to 1.1.1.1 (bypass Mullvad DNS)
  • Quantum-secure VPN tunnel (Thanks Mullvad - not sponsored)
  • Priority on real Open Source tooling and solutions
  • DMZ for public facing server with various services
Critical Security Requirements

Nothing leaves through WAN without VPN tunnel. This means:

  • No information leaks to ISP
  • No DNS leaks
  • No WebRTC leaks
  • Exception: Services like Plex that require direct connectivity

The goal is complete privacy - your ISP should see only encrypted VPN traffic.

DNS Architecture

The DNS setup uses three services working together: AdGuard filters queries, DNSmasq handles local domains, and Unbound forwards to Mullvad DNS through the VPN tunnel.

Important: Install AdGuard First

Start with AdGuard Install

Since we are modifying default DNS & traffic flow, I highly recommend that you install AdGuard before following this guide. Installing it mid-setup while DNS is being reconfigured can cause temporary DNS resolution failures.

You don’t need to enable it until later, however installing it early can prevent issues with DNS resolution for https://www.routerperformance.net/mimugmail-single.conf

DNS Query Flow

graph TD
    Client[All Client Networks
    LAN, DMZ, SecWiFi, IoT, Guest, GoatNET]
    
    Client -->|Port 53| Router{NAT Redirect Rule}
    
    Router -->|DNS_ADGUARD_IG
    LAN, DMZ, SecWiFi
    IoT, Guest| AdGuard[AdGuard Home :53
    DNS Filtering]
    Router -->|NO_ADGUARD_IG
    GoatNET VLAN46| CF[Cloudflare 1.1.1.1
    via VPN]
    
    AdGuard -->|Local Domains| DNSmasq[DNSmasq :5335
    Local Resolution]
    AdGuard -->|All Other Queries| Unbound[Unbound :5353
    Upstream Resolver]
    
    Unbound -->|All Queries
    via VPN| Mullvad[Mullvad DNS
    100.64.0.X]
    
    CF -.->|Response| Client
    Mullvad -.->|Response| Unbound
    Unbound -.->|Response| AdGuard
    DNSmasq -.->|Response| AdGuard
    AdGuard -.->|Response| Client

Understanding the Flow

AdGuard receives all DNS queries on port 53 (DNS) and uses conditional forwarding to route them. Local domains home.byteron.com and reverse DNS for 10.x.x.x go to DNSmasq on port 5335. Everything else goes to Unbound on port 5353, which forwards through the VPN tunnel to Mullvad DNS.

ℹ️ VLAN46 DNS Routing

VLAN46 (GoatNET) has a NAT redirect rule that sends DNS queries directly to Cloudflare 1.1.1.1, bypassing both AdGuard and Mullvad DNS filtering. However, this traffic still routes through the VPN tunnel . Only the DNS resolver changes, not the routing path. This provides the unfiltered DNS needed for marketing/analytics work while maintaining VPN privacy.

AdGuard Upstream DNS Configuration

The bracket syntax [/domain/]server:port creates conditional forwarding rules in AdGuard. Lines without brackets define the default upstream server.

[/home.byteron.com/]10.0.0.1:5335    # Local domain → DNSmasq
[/10.in-addr.arpa/]10.0.0.1:5335     # Reverse DNS → DNSmasq
10.0.0.1:5353                        # Everything else → Unbound

DNS services & Ports

ServicePortPurpose
AdGuard53DNS filtering and query routing
DNSmasq5335Local domain resolution and DHCP
Unbound5353Upstream resolver to Mullvad DNS

Security outcome: Internal DNS structure never leaks outside your network, all external queries travel through encrypted VPN tunnels, and AdGuard filtering protects all networks except VLAN46 (which bypasses AdGuard for work requirements).

Additional Security Tools

Once everything is set up and running, I am planning to also implement the following:

ToolPurpose
ZenarmorNext-generation firewall features and application control
CrowdSecCollaborative security engine for threat detection and response
Suricata IPS/IDSIntrusion Prevention and Detection System
Wazuh SIEMSIEM Tool

To keep the length of this blog reasonable I have to leave this for the future.


Hardware

Hardware selection was somewhat of a headache depending on the levels of risk you were willing to accept.

If nation states, hardware backdoors, etc are a concern for you, then you are pretty much out of affordable options.

Power consumption, noise and costs were some of my priorities when deciding the hardware. My old PFsense was running on a second hand Dell Optiplex 3080 with an i340-T4 Quad Port NIC. It was old and noisy and got quite hot by todays standards. It’s been chugging along for many years and already ROIed 2-3 times over. I think it earned its retirement or a new role maybe? Nothing planned for now.

After comparing various options from Aliexpress, Local EU market and official devices from OPNsense the choices were narrowed down to a Small Form factor PC. Either a newer Dell Optiplex or something from Lenovo Thinkcenter series.

In the end I went for Lenovo M920q with a i350-T4 Quad Port NIC and a riser card. The total costs were under 230 euros, compared to DEC677 from OPNsense which is around 549, we are achieving similar or better performance for half the price.

That sounds like a good value proposition, no wonder so many people have done this exact path.

Here are some pictures of me taking it apart, installing the riser and i350-T4 NIC. I also 3D-printed a backplate cover from Makerworld which snapped immediately as I took it of the print plate. I did some soldering magic and fixed the break, installed the shield and we have our little box ready.

If required you should update, reset and configure BIOS. You can refer to this github link for more details on the steps related to this.


Software & USB Setup

⚠️ Linux-Specific Instructions

These steps are for Linux systems. macOS and Windows have different procedures. If you’re unfamiliar with burning ISO images to USB drives, research the process for your operating system before proceeding.

ℹ️ Recommended Tools

If you prefer GUI tools over command-line, consider using:

  • balenaEtcher (Windows/macOS/Linux) - User-friendly, cross-platform
  • Rufus (Windows) - Fast and reliable
  • Ventoy (Windows/macOS/Linux) - Supports multiple ISOs on one USB

All produce identical results - use whichever you’re comfortable with.

Downloading OPNsense

Download the latest OPNsense ISO from: https://opnsense.org/download/

Preparing the USB Drive

Identify Your USB Device

lsblk

Locate your USB drive (typically /dev/sdd or similar). Note the device path.

Wipe the USB Drive

Data Loss Warning

This will permanently erase all data on the USB drive. Backup any important files first.

sudo dd if=/dev/zero of=/dev/sdd bs=4M status=progress

Replace /dev/sdd with your actual USB device path.

Verify the Downloaded Image

openssl sha256 OPNsense-25.7-vga-amd64.img.bz2

Compare the output with the SHA256 checksum on the OPNsense download page.

SHA256 for version 25.7:

705e112e3c0566e6e568605173a8353a51d48074d48facf5c5831d2a0f7fb175

For additional verification methods, refer to the OPNsense documentation.

Burn the Image to USB

sudo dd if=OPNsense-25.7-vga-amd64.img of=/dev/sdd bs=16k status=progress

Installing OPNsense

Pre-Installation Checklist

Before booting from USB, ensure:

  • BIOS settings have been configured (as covered in the hardware setup section)
  • Boot order prioritizes USB devices
  • Secure Boot is disabled (if applicable)

Installation Process

  1. Boot from USB
    Insert the USB drive and boot your system. Select the USB drive from the boot menu.
  2. Follow Installer Prompts
    The OPNsense installer will guide you through the setup process. Select your preferred installation options (typically default settings work well).
  3. Verify Network Interfaces
    Confirm that all network interfaces are detected. For the intel i350-T4 NIC, you should see igb0, igb1, igb2, and igb3 listed.
  4. Set Root Password
    Create a strong root password and store it securely. You will need this to access OPNsense.

NIC Compatibility: Non-Intel NICs or certain riser cards may have compatibility issues with OPNsense. The Intel i350-T4 has excellent driver support and is recommended for this build.

Network Interface Assignment

During installation, assign your WAN and LAN interfaces:

InterfacePhysical PortPurpose
igb0Port 1WAN (Internet connection)
igb1Port 2LAN (Local network)
igb2Port 3Unassigned (for future use)
igb3Port 4Unassigned

Integrated Interface: The M920q includes a built-in network interface (typically em0, though naming may vary). This is normal and can be left unassigned or used for management if desired.

Physical Port Labels: Label your ports with WAN/LAN designations using tape or a label maker. This prevents accidentally plugging cables into the wrong ports, which could expose your network or cause connectivity issues.

VLAN Configuration

We do not configure VLANs during installation. These will be set up later through the web interface.

However, with right knowledge you can setup VLANs here as well, it is completely up to you.

Post-Installation

Once the installation is complete, remove the USB and reboot the system. When it loads back up, the console will display the IP address for OPNsense web interface which you can reach through any browser.


Setting up OPNsense using UI

Initial Login

Access the web UI using the credentials: root / [your_password]

Setup Wizard

You’ll be presented with a setup wizard. You can either proceed through it or skip and configure manually.

Recommended Configuration:

Feel free to use your prefered DNS providers instead.

SettingValueNotes
Domainhome.byteron.comUse your own domain
Primary DNS Server1.1.1.1Temporary - will be replaced later
Secondary DNS Server9.9.9.9Temporary - will be replaced later
Override DNS✗ UncheckCritical - prevents ISP DNS usage
No ISP DNS

We do not want to use our ISP DNS, so unchecking Override DNS is crucial. This ensures your DNS queries won’t leak to your ISP.

Optional: DNSSEC Configuration

If you want to enable DNSSEC support you should enable the following settings.

SettingValue
Enable DNSSEC Support✓ Check
Harden DNSSEC data✓ Check

User Access, SSH and Keys

Depending on how you are setting this up I’d recommend to enable SSH, at the very least temporarily so that you can troubleshoot any issues as they arise.

Once you’ve done with the setup, I recomend only having certificate or key based authentication for SSH. Password based SSH is acceptable during setup only. Make a mental note on this going forward.

Disable IPv6

If you do not require IPv6 then you need to disable it in two locations found here:

LocationSettingValue
System → Settings → GeneralPrefer IPv4 over IPv6✓ Check
Interfaces → SettingsAllow IPv6✗ Uncheck
Screenshot showing prefer IPv4 Screenshot showing how to disable IPv6

Optional: Hardware Offloading

If you’re not using Intel NICs, you may want to disable hardware offloading options that can be found here: Interfaces → Settings

Setting up VLANs

Navigate to Interfaces → Devices → VLAN and create the following VLANs:

VLAN IDNamePurposeParent Interface
VLAN10DMZPublic-facing serverigb2
VLAN40SecWiFiSecure WiFiigb1
VLAN42IoT WiFiIoT devicesigb1
VLAN44Guest WiFiGuest networkigb1
VLAN46GoatNETBypass DNS/GeoBlockingigb1
VLAN90ReservedOptional for future useigb1
ℹ️ DMZ Isolation

Notice that VLAN10 (DMZ) uses igb2 as its parent interface while all other VLANs use igb1. This is intentional - we’re dedicating a separate physical interface for DMZ traffic performance isolation.

To do this, our OPNsense needs to have at least 3 ports, one for LAN, one for WAN and one for DMZ.

Once done, it should look like this:

OPNsense VLAN configuration showing VLAN10-46 assignments on igb1 and igb2 interfaces

Physical Port Configuration

OPNsense connects to two ports on the 24-port switch:

Switch PortOPNsense InterfaceTypeTraffic
Port 23igb1TrunkLAN, VLAN40, VLAN42, VLAN44, VLAN46
Port 24igb2DedicatedDMZ VLAN10 only

Note that igb1 and igb2 refer to the two physical interfaces on OPNsense.

💡 Switch VLAN setup

Details on how to set up network switches and VLANs can be found here

Assigning VLANs to Interfaces

Navigate to Interfaces → Assignments and assign each VLAN to a virtual interface. Once complete, your assignments should include all the VLANs listed above.


Note that I redacted a few fields as I forgot to take a screenshot earlier in the setup.

Interface Assignment Screenshot

Configuring IP Addresses

Now the tedious part, for each VLAN interface, you need to:

  1. Enable the interface
  2. Assign a static IP address

IP Addressing Scheme

To keep things organized, the third octet matches the VLAN ID:

Base: 10.0.0.1/24 (LAN)
Pattern: 10.0.[VLAN_ID].1/24

Examples:

  • VLAN10 → 10.0.10.1/24
  • VLAN40 → 10.0.40.1/24
  • VLAN46 → 10.0.46.1/24

Navigate to each interface (e.g., Interfaces → [DMZ_VL10]) and configure it with the appropriate static IP following this pattern.

Repeat this process for all VLAN interfaces as shown in the screenshot for DMZ_VL10

Example Screenshot for setting up VLAN interfaces

DNSmasq & DHCP

We’ll use DNSmasq for local DNS resolution and DHCP services across all VLANs.

Configuring DNSmasq

Navigate to Services → DNSmasq DNS & DHCP → General:

  1. Select the interfaces that need the service
  2. Enable the service
  3. Change the listening port from 53 to 5335 (since AdGuard will use port 53)

Setting Up DHCP Ranges

Navigate to Services → DNSmasq DNS & DHCP → DHCP Ranges and create IP ranges for each VLAN.

DHCP Range Strategy

Each network uses the same range pattern (.100 to .150), providing 51 IP addresses per network. This makes management consistent across all VLANs. Adjust ranges if you need more devices per VLAN.

NetworkGatewayDHCP StartDHCP EndAvailable IPs
LAN10.0.0.110.0.0.10010.0.0.15051
VLAN10 (DMZ)10.0.10.110.0.10.10010.0.10.15051
VLAN40 (SecWiFi)10.0.40.110.0.40.10010.0.40.15051
VLAN42 (IoT)10.0.42.110.0.42.10010.0.42.15051
VLAN44 (Guest)10.0.44.110.0.44.10010.0.44.15051
VLAN46 (GoatNET)10.0.46.110.0.46.10010.0.46.15051

Example screenshot of editing DHCP range for LAN. Repeat this process for each network in the table above.

Edit DHCP Range Screenshot

Once configured, your DHCP ranges should look like this:

DHCP Range Image

VPN Setup: Mullvad + WireGuard

💡 The only VPN you need

I’ve used Mullvad as my VPN of choice for many years. If you want a privacy-focused, performant VPN, Mullvad is the choice. I’m not sponsored by them, but I’d recommend them 10/10 times.

Generating Configuration Files

Use Mullvad’s WireGuard Config Generator to create configuration files for your tunnels.

  1. Generate a new key or use an existing one
  2. Select your preferred country, city, and server
  3. Download and open the config file

Example Configuration File:

[Interface]
# Device: Funky Name
PrivateKey = REDACTED_PRIVATE_KEY
Address = 10.65.196.16/32
DNS = 10.64.0.1

[Peer]
PublicKey = REDACTED_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = 185.209.196.77:51819

Note down all fields - you’ll need them in the following steps.

Creating WireGuard Peers

Navigate to VPN → WireGuard → Peers and create a peer for each server:

FieldValue
Namede-fra-wg-007
Public Key[PublicKey from config]
AllowedIPs0.0.0.0/0
Endpoint Address[Endpoint from config]
Endpoint Port[Endpoint port from config]
Keepalive25

Leave the Instance field blank for now.

Wireguard VPN Peer Screenshot

Creating WireGuard Instances

Navigate to VPN → WireGuard → Instances and create an instance:

FieldValue
NameDE_VPN
Public Key[PublicKey from config]
Private Key[PrivateKey from config]
Listen Port51819
Tunnel Address10.65.196.16/32 [Interface Address from config]
Peersde-fra-wg-007
Wireguard VPN instance Screenshot

Repeat for Second VPN

Create a second peer and instance for redundancy. Make sure that you use a different listen port for each VPN instance.

VPN InstanceListen PortPurpose
VPN_1 (UK_VPN)51820Primary VPN tunnel
VPN_2 (DE_VPN)51819Secondary VPN tunnel (redundancy)
VPN_3 (example)51818Example Third VPN tunnel
⚠️ Add a peer to each instance

Make sure that each instance has at least 1 peer assigned to it.

For each instance you may want to assign multiple peers.

For example, DE_VPN instance may have 2+ peers, each being a different server like de-fra-wg-007 and de-fra-wg-001

Enabling and Verifying Tunnels

  1. Enable WireGuard in the Instance tab
  2. Navigate to VPN → WireGuard → Status
  3. Verify connection status shows recent handshakes and data transfer

It should look something like this:

WireGuard VPN status showing active handshakes for UK and DE tunnels

Assigning WireGuard Interfaces

Navigate to Interfaces → Assignments and add both WireGuard interfaces wg0 and wg1. Your interface list should now include all VLANs, LAN, WAN, and the two WireGuard interfaces.

Final Interface Assignment Image

Configuring WireGuard Interface Settings

For each WireGuard interface, you need to enable it but NOT assign a gateway at the interface level.

Navigate to Interfaces → [UK_VPN] (or whatever you named your first VPN interface):

FieldValue
Enable✓ Check
LockUnchecked
DescriptionUK_VPN (or your chosen name)
IPv4 Configuration TypeNone
IPv6 Configuration TypeNone
Wireguard Interface Screenshot

VPN Gateways

Creating Gateway Entries

Navigate to System → Gateways → Configuration and create a gateway for each VPN tunnel:

FieldVPN 1 (Primary)VPN 2 (Secondary)
NameWAN_UK_VPNWAN_DE_VPN
Interface[Your VPN interface]DE_VPN
Address FamilyIPv4IPv4
Priority12
IP Address[Config Address - 1]*10.65.196.15
Far Gateway
Monitor IP100.64.0.2100.64.0.1
ℹ️ Gateway IP Calculation

Take the tunnel address from the config file and subtract 1 from the last octet:

If tunnel address is: 10.65.196.16/32
Gateway IP would be: 10.65.196.15

This only works if your tunnel address ends with a number greater than 1. Mullvad’s WireGuard implementation doesn’t actually use a traditional gateway model so you could technically use ANY IP in the Mullvad network range.

WAN_DE_VPN Gateway screenshot
ℹ️ Monitor IPs

The Monitor IP addresses 100.64.0.1 and 100.64.0.2 are Mullvad DNS servers used to check gateway health.

You can find the list of Mullvad DNS servers and what they block on Github

Modify WAN Gateway

Edit the WAN_DHCP gateway and change its priority to 254 (lowest priority).

Creating Gateway Group

Navigate to System → Gateways → Group and create a failover group:

FieldValue
Group NameWAN_VPN_GRP
Gateway PriorityWAN_UK_VPN: Tier 1
WAN_DE_VPN: Tier 2
WAN_DHCP: Never
Trigger LevelMember Down or Packet Loss/High Latency
OPNsense gateway group configuration with tier 1 and tier 2 failover priority

Static Routes

Critical Configuration

Static routes are essential for VPN functionality. Without them, you create a circular routing problem: traffic can’t establish the VPN tunnel because all traffic is being routed through the tunnel that doesn’t exist yet.

Understanding the Requirement

You must create a static route for every VPN endpoint IP address from your WireGuard configuration files. Even if you only plan to use one tunnel initially, add routes for all endpoints you’ve configured as peers. This ensures seamless failover if you later enable backup tunnels.

Creating Static Routes

Navigate to System → Routes → Configuration and add a static route for each VPN endpoint.

Example Routes:

For each WireGuard peer you created earlier, add its endpoint:

Edit Static Route Sreenshot

Route 1 - UK Server:

FieldValue
Network Address185.65.135.202/32
GatewayWAN_DHCP
DescriptionVPN WAN Bypass for uk-lon-wg-101

Route 2 - DE Server:

FieldValue
Network Address185.209.196.77/32
GatewayWAN_DHCP
DescriptionVPN WAN Bypass for de-fra-wg-007
⚠️ Adding New VPN Peers Later

If you add additional WireGuard peers in the future, you must create static routes for their endpoint IPs as well. Forgetting this will cause connection failures due to circular routing.

Finding Your Endpoint Addresses

The endpoint addresses come from the [Peer] section of your Mullvad WireGuard configuration files:

[Peer]
PublicKey = REDACTED_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0
Endpoint = 185.209.196.77:51819  ← This IP address

Extract the IP address (without the port) and use it in the Network Address field with /32 suffix. Once done, it should look like this:

Static Route Sreenshot

Note that you see 4 entries for 4 different servers/regions.

Verification

After adding all static routes, verify they appear in your routing table. You can go to System → Routes → Status and you can filter by WAN. You should see entries for each VPN endpoint IP routing through your WAN gateway.

Alternative Static Route Sreenshot

DNS Resolver (Unbound)

General Configuration

Navigate to Services → Unbound DNS → General and configure the basic settings. Make sure to change the listen port to 5353 so it does not conflict with AdGuard.

SettingValue
Network InterfacesLAN, VLAN10, VLAN40, VLAN42, VLAN44, VLAN46
Listen Port5353
DNSSEC
DHCP Registration
DHCP Static Mappings
Local Zone Typestatic

Advanced Mode Settings

Enable Advanced Mode (top left) to access additional options:

SettingValue
Outgoing Network InterfacesDE_VPN, UK_VPN
Prevent DNS Leaks

Do not include WAN in the Outgoing Network Interfaces. This ensures all DNS queries go through the VPN tunnels, preventing DNS leaks to your ISP.

If DNS resolution fails during setup, the issue is elsewhere:

  • Verify VPN tunnels are active (VPN → WireGuard → Status)
  • Check static routes are configured
  • Confirm gateway group is working
  • Verify AdGuard is running on port 53

Adding WAN as an outgoing interface creates the exact DNS leak this setup prevents.

Temporary troubleshooting only: If you must add WAN to diagnose issues, remove it immediately after identifying the problem. This creates a DNS leak and exposes your queries to your ISP.

Once done, it should look something like this:

Unbound DNS resolver general settings with port 5353 and VPN outgoing interfaces

Advanced Tab Configuration

Navigate to the Advanced tab and enable the following security options:

SettingValue
Hide Identity✓ Enable
Hide Version✓ Enable
Prefetch Support✓ Enable
Prefetch DNS Key Support✓ Enable
Harden DNSSEC data✗ Disable
💡 DNSSEC Performance

“Harden DNSSEC data” adds validation overhead. If you experience slow DNS (>200ms), try disabling this setting first.

Creating Custom Configuration Files on OPNsense

SSH into OPNsense and create the necessary configuration files for local domain handling.

Optional: Install nano

I like to use nano text editor which does not come with OPNsense so you have to install it yourself.

pkg install nano

You can use your prefered editor in the steps below, just replace nano command.

Create the targets file:

nano /usr/local/opnsense/service/templates/OPNsense/Unbound/+TARGETS

Content:

private_domains.conf:/usr/local/etc/unbound.opnsense.d/private_domains.conf

Create the private domains configuration:

nano /usr/local/etc/unbound.opnsense.d/private_domains.conf

Content:

server:
  local-data: "home.byteron.com. 3600 IN SOA opnsense.home.byteron.com. admin.byteron.com. 2021110201 86400 7200 3600000 3600"
  local-zone: "home.byteron.com" static

Apply and verify the configuration:

# Generate template
configctl template reload OPNsense/Unbound

# Show generated file
cat /usr/local/etc/unbound.opnsense.d/private_domains.conf

# Check if configuration is valid
configctl unbound check

Query Forwarding

Navigate to Services → Unbound DNS → Query Forwarding and configure forwarding rules:

DomainServer IPServer PortPurpose
home.byteron.com10.0.0.15335Forward local domain queries to DNSmasq
10.in-addr.arpa10.0.0.15335Forward reverse DNS queries to DNSmasq
[Leave blank]**100.64.0.153Forward all other queries to Mullvad DNS
[Leave blank]**100.64.0.253Forward all other queries to Mullvad DNS_2
[Leave blank]**100.64.0.353Forward all other queries to Mullvad DNS_3

** Leave the Domain field blank for default upstream servers that handle all non-local queries.

Note that I included 3 Mullvad DNS servers so that in rare cases where one of them goes down, we are still able to resolve public domains.

⚠️ What is in-addr.arpa?

10.in-addr.arpa is a special Internet domain used for reverse DNS lookups of IP addresses within the private 10.0.0.0/8 address space.

You can learn more on Reverse DNS lookup wiki.

Once completed, it should look like this. Also note that we have disabled entry for 1.1.1.1 which was used for testing purposes.

Unbound Query Forwarding Screenshot

Firewall Configuration

Now we need to configure firewall rules to allow traffic, since the default action is always BLOCK.

ℹ️ Credit

The interface group approach is inspired by Michael Schnerring’s OPNsense guide. This method simplifies rule management significantly.

Creating Interface Groups

Navigate to Firewall → Groups and create the following groups:

Group NameMember InterfacesPurpose
VPN_OUT_IGLAN, VLAN10, VLAN40, VLAN42, VLAN44, VLAN46Interfaces for VPN-bound traffic
SEC_LAN_IGLAN, VLAN40Secure/trusted networks (LAN + SecWiFi)
DNS_ADGUARD_IGLAN, VLAN10, VLAN40, VLAN42, VLAN44Networks using AdGuard DNS + Blocking
NO_ADGUARD_IGVLAN46Networks bypassing AdGuard (No Blocking)
WAN_OUT_IG****See Note belowWAN-bound traffic (selective routing)

It should look something like this:

Firewall Groups Screenshot
⚠️ WAN_OUT_IG note

We leave WAN_OUT_IG empty in this configuration since nothing routes directly through WAN (everything goes through VPN).

You cannot create an interface group without any members, so if you don’t plan to use WAN bypass routing, skip creating this group entirely. You should also skip WAN_OUT_IG rules that are created later.

You would create this group and rules if you have a specific interface group that requires direct WAN access, for example a specific VLAN. In my setup, this behaviour is not wanted or needed.

Firewall Aliases

Aliases simplify firewall rule management and make maintenance easier. Navigate to Firewall → Aliases to create the following.

Network & Host Aliases

My setup has aliases for every device on the network which includes servers, cameras, printers, network gear and everything else. There are also nested aliases to help group devices further. I recommend you do the same according to your needs. Since my alias list contains sensitive information I am unable to share a screenshot for it.

NameTypeContentDescription
IPv4_RFC1918Networks10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
Private IPv4 RFC1918 addresses
SELECTIVE_ROUTINGHosts[See note]External Hosts reachable directly through WAN bypassing VPN
MULLVAD_ENDPOINTSHosts[Endpoint addresses from config]VPN endpoint addresses for tunnel establishment
⚠️ SELECTIVE_ROUTING Note

This is an alias for EXTERNAL host that you wish to reach through WAN and bypass VPN.

For example, netflix.com

Port Aliases

NameTypeContentDescription
PORTS_ANTI_LOCKOUTPort(s)443, 22OPNsense admin interface ports
ALL_PORTSPort(s)1:65535All ports (use for troubleshooting)
PORTS_OUT_LANPort(s)See table belowIntranet allowed ports
PORTS_OUT_WANPort(s)See table belowInternet allowed ports
SERVICE_PORTSPort(s)[Not shown - customize per your services]Ports for DMZ services

Optional: Aliases:

NameTypeContentDescription
PLEX_PORTSPort(s)443, 1900, 3005, 8324, 32400, 32469, 32510:32414Plex server required ports
FILE_SHARINGPort(s)137:139, 445NetBIOS & SMB ports
LOCAL_PROXYHost(s)10.0.0.YReverse Local Proxy IP
EXTERNAL_PROXYHost(s)10.0.0.ZReverse Proxy IP
PLEXHost(s)10.0.10.XIP address for PLEX
STEAM_PORTSPort(s)4380, 27000:27100Steam client and game servers
💡 Optional Aliases

The optional aliases are examples you may want depending on your specific setup and services. Adjust IP addresses to match your actual server locations. Create only the aliases you need for your specific setup.

PORTS_OUT_LAN - Intranet allowed ports:

Port(s)Service
5353:5354mDNS
123NTP
137NetBIOS
21FTP
22SSH
161SNMP
80HTTP
8080HTTP alt / UniFi communication
443HTTPS
8443HTTPS alt / UniFi GUI/API
8880UniFi HTTP portal redirection
10001UniFi device discovery
5001iPerf
5900VNC
3389RDP
3080AdGuard UI
49152:65535Ephemeral ports

PORTS_OUT_WAN - Internet allowed ports:

Port(s)Service
21FTP
22SSH
80HTTP
8080HTTP alt
443HTTPS
8443HTTPS alt
465SMTPS
587SMTPS
993IMAPS
49152:65535Ephemeral ports

My PORTS_OUT_WAN alias also contains other aliases such as STEAM_PORTS, TOOL_PORTS, APPLICATION_PORTS etc. The process of figuring out what application requires what port can be tedious but can be easily done by utilizing Firewall → Log Files → Live View and adding appropriate filters for interfaces, source devices and action being blocked. With the filter set, you simply launch the application and by looking at the logs you can easily figure out what ports are needed. Hint: They are the ones getting blocked.

⚠️ Egress Filtering Complexity

Egress filtering can be challenging to manage. If you encounter issues with applications not working, consider using the ALL_PORTS alias for PORTS_OUT_WAN or PORTS_OUT_LAN to simplify troubleshooting. You can always tighten the rules once everything is working.

What are MULLVAD_ENDPOINTS for?

The MULLVAD_ENDPOINTS alias contains your VPN endpoint addresses and will be used to create rules that allow tunnel establishment even when all traffic is routed through the VPN. This solves the circular routing problem mentioned earlier.


NAT - Network Address Translation

Outbound NAT Rules

Navigate to Firewall → NAT → Outbound and select Manual Outbound NAT rule generation.

Create the following outbound NAT rules:

InterfaceSource AddressDescription
WANWAN_OUT_IG netWAN_OUT_IG to WAN
WANVPN_OUT_IG netVPN_OUT_IG to WAN
UK_VPNVPN_OUT_IG netVPN_OUT_IG to UK VPN
DE_VPNVPN_OUT_IG netVPN_OUT_IG to DE VPN

It should look like this once done:

OPNsense outbound NAT rules for WAN and VPN interfaces

NAT DNS Redirect

Navigate to Firewall → NAT → Port Forward to redirect DNS queries to the appropriate DNS resolver.

Redirect networks using AdGuard:

FieldValue
InterfaceDNS_ADGUARD_IG
ProtocolTCP/UDP
SourceDNS_ADGUARD_IG net
Destinationany
Destination Port Range53 (DNS)
Redirect Target IP10.0.0.1
Redirect Target Port53 (DNS)
DescriptionRedirect DNS_ADGUARD_IG networks to AdGuard Home

Redirect VLAN46 to bypass AdGuard:

FieldValue
InterfaceNO_ADGUARD_IG
ProtocolTCP/UDP
SourceNO_ADGUARD_IG net
Destinationany
Destination Port Range53 (DNS)
Redirect Target IP1.1.1.1
Redirect Target Port53 (DNS)
DescriptionRedirect GoatNET DNS directly to 1.1.1.1 (bypass AdGuard)

This traffic still routes through the VPN tunnels but uses Cloudflare DNS instead of Mullvad DNS.

Optional: Reverse Proxy Redirect

If running a reverse proxy, create this port forward rule. You may also want near identical rule for HTTP traffic.

FieldValue
InterfaceWAN
ProtocolTCP
SourceAny
DestinationWAN address
Destination Port Range443 (HTTPS)
Redirect Target IPPROXY [Alias]
Redirect Target PortXXXX
DescriptionWAN redirect port 443 to PROXY port XXXX
ℹ️ Reverse Proxy Listen Port

Replace XXXX with the actual port your reverse proxy listens on. This is configured when you set up your reverse proxy.

Optional: Plex Remote Access

For Plex remote access, create this port forward rule:

Navigate to Firewall → NAT → Port Forward and click Add to create a new rule:

FieldValue
InterfaceWAN
ProtocolTCP
SourceAny
DestinationWAN address
Destination Port Range32400
Redirect Target IPPLEX [Alias]
Redirect Target Port32400
DescriptionRedirect WAN to PLEX on 32400
NAT reflectionDisable
Filter rule associationPass
⚠️ Port Forward Security Trade-off

This port forward exposes your Plex server directly to the internet on WAN, bypassing VPN protection.

While necessary for Plex remote access, be aware of its security implications.

Once completed, it should look like this:

Firewall NAT port forward rules Screenshot

Firewall Rules

Floating Rules

Navigate to Firewall → Rules → Floating and create the following rules:

ActionInterfaceProtocolSourceDestinationDest. PortGatewayDescription
Pass*TCP/UDP*This FirewallPORTS_ANTI_LOCKOUT*Anti-Lockout rule for LAN
Pass*TCP/UDPThis FirewallMULLVAD_ENDPOINTS*WAN_DHCPAllow VPN tunnel establishment
Pass*TCP/UDPPLEX!IPv4_RFC1918PLEX_PORTSWAN_DHCPPLEX bypass VPN to WAN
PassGoatNET_VL46*GoatNET_VL46 net!IPv4_RFC1918*WAN_VPN_GRPAllow GoatNET through WAN_VPN_GRP
BlockSEC_LAN_IGTCP/UDPSEC_LAN_IG net!IPv4_RFC191853 (DNS)*Block external DNS - prevent leaks
BlockWAN*VPN_OUT_IG net!IPv4_RFC1918*WAN_DHCPKill Switch: Block all other WAN traffic

*Enable logging on the kill switch rule to monitor blocked traffic.

Optional: Plex Bypass VPN

ActionInterfaceProtocolSourceDestinationDest. PortGatewayDescription
PassLANTCP/UDPPLEX!IPv4_RFC1918PLEX_PORTSWAN_DHCPPLEX bypass VPN to WAN

Once done it should look like this. I’ve redacted some additional rules that are unique to my network.

OPNsense floating firewall rules with anti-lockout and VPN kill switch

SEC_LAN_IG Rules

Navigate to Firewall → Rules → SEC_LAN_IG and create the following rules:

ActionProtocolSourceDestinationDest. PortDescription
PassTCP/UDPSEC_LAN_IG netDMZ_VL10 netFILE_SHARINGAllow SEC_LAN_IG to DMZ for SMB & NetBIOS
PassTCP/UDPSEC_LAN_IG netDMZ_VL10 netSERVICE_PORTSAllow SEC_LAN_IG to DMZ services
PassTCP/UDPSEC_LAN_IG netSEC_LAN_IG netPORTS_OUT_LANAllow SEC_LAN intranet traffic
PassICMPSEC_LAN_IG net*-Allow SEC_LAN intranet pings

Rule Explanation:

  1. FILE_SHARING rule: Allows LAN and SecWiFi to access file shares (SMB/NetBIOS) on DMZ server
  2. SERVICE_PORTS rule: Allows access to other DMZ services (web interfaces, APIs, etc.) defined in SERVICE_PORTS alias (Common examples: 80, 443, 8080, 8443, or application-specific ports)
  3. Intranet traffic rule: Allows LAN and SecWiFi devices to communicate with each other on specified ports
  4. ICMP rule: Allows pinging from SEC_LAN_IG networks for troubleshooting
ℹ️ DMZ Isolation & Security Model

Access to DMZ is controlled through explicit SEC_LAN_IG rules (rules 1 and 2 above), not through interface group membership.

DMZ Network Isolation:

  • DMZ (VLAN10) is included in VPN_OUT_IG and DNS_ADGUARD_IG for internet and DNS access
  • DMZ is NOT included in SEC_LAN_IG - this is intentional for security
  • Access to DMZ services is controlled through explicit SEC_LAN_IG interface rules only
  • This prevents DMZ from initiating connections back to trusted networks (LAN/SecWiFi)

Why this matters: If your DMZ server is compromised, the attacker cannot scan or access your personal devices on LAN/SecWiFi. They’re limited to what’s explicitly allowed through firewall rules.

Firewall SEC_LAN_IG rules Screenshot

Optional: WAN_OUT_IG Rules

⚠️ If you need WAN Bypass for specific interfaces

As mentioned earlier, if you need specific interface traffic to bypass the VPN and use WAN directly then you need to make sure that the necessary interface is added to WAN_OUT_IG interface group which wasn’t created earlier.

Then create the rule below.

ActionProtocolSourceDestinationDest. PortDescription
PassTCP/UDPWAN_OUT_IG net!IPv4_RFC1918PORTS_OUT_WANAllow internet traffic through WAN for WAN_OUT_IG
💡 What destination is !IPv4_RFC1918?

!IPv4_RFC1918 means external traffic (NOT private IPv4 traffic).

VPN_OUT_IG Rules

Navigate to Firewall → Rules → VPN_OUT_IG:

Optional: Selective Routing

ActionProtocolSourceDestinationDest. PortGatewayDescription
PassTCP/UDPVPN_OUT_IG netSELECTIVE_ROUTINGPORTS_OUT_WAN-Allow selected internet traffic through WAN
ℹ️ Selective Routing

The SELECTIVE_ROUTING alias is currently empty but can be populated with hosts that need to bypass VPN routing.

This rule is not actively used in this configuration and only necessary for some edge cases. For example, sites that block access through VPN.

Route VPN traffic through gateway group:

ActionProtocolSourceDestinationDest. PortGatewayDescription
PassTCP/UDPVPN_OUT_IG net!IPv4_RFC1918PORTS_OUT_WANWAN_VPN_GRPAllow VPN_OUT_IG internet traffic through VPN_GRP
Firewall VPN_OUT_IG rules Screenshot

Optional: LAN to ALL Networks

ActionProtocolSourceDestinationDescription
PassTCP/UDPAnyAnyAllow LAN access to ALL networks

This rule should remain disabled but can be quickly enabled for troubleshooting when you need LAN devices to access all networks.

Optional: Cleanup for PORTS_OUT_LAN

When finalizing your configuration, consider removing the following from PORTS_OUT_LAN if you do not need them:

  • Port 53 (DNS): NAT redirect rules handle DNS routing
  • Port 623 (IPMI): Only needed if you have hardware management interfaces (Dell iDRAC, HP iLO, etc.)
  • Port 137 (NetBIOS): Used for network scanning; blocking can prevent unwanted discovery traffic. We do use Port: 137 for the FILE_SHARING rule on SEC_LAN_IG to DMZ

AdGuard Home Setup

Installing AdGuard Home

AdGuard Home requires adding a third-party repository. SSH into OPNsense and run:

fetch -o /usr/local/etc/pkg/repos/mimugmail-single.conf https://www.routerperformance.net/mimugmail-single.conf

Once the repository is added, navigate to System → Firmware → Plugins in the OPNsense UI and install os-AdGuardhome-maxit.

Enabling AdGuard Home

Navigate to Services → AdGuard and enable the service:

  • ✓ Enable
  • ✓ Primary DNS
⚠️ Port Configuration

If AdGuard doesn’t start properly or you need to verify the port configuration, SSH into OPNsense and manually edit the config:

nano /usr/local/AdGuardHome/AdGuardHome.yaml

Ensure the DNS section specifies Port 53 (DNS):

dns:
  bind_hosts:
    - 10.0.0.1
  port: 53

Accessing AdGuard UI

Add port 3080 to PORTS_OUT_LAN

Make sure you add port 3080 to your PORTS_OUT_LAN alias so that you can access AdGuard web interface.

Both LAN and SecWiFi can access AdGuard web interface.

Access the UI at: http://10.0.0.1:3080/

Configuring DNS Settings

Navigate to Settings → DNS Settings in the AdGuard UI.

Upstream DNS servers:

[/home.byteron.com/]10.0.0.1:5335
[/10.in-addr.arpa/]10.0.0.1:5335
10.0.0.1:5353

Fallback DNS servers:

10.0.0.1:5353

Bootstrap DNS servers:

10.0.0.1:5353

Private reverse DNS servers:

10.0.0.1:5335

Click Test Upstream DNS to verify connectivity, then Save.

Update System DNS Configuration

Navigate to System → Settings → General and update the DNS configuration:

FieldValue
DNS Server10.0.0.1
Gatewaynone

Remove any other DNS entries to ensure all DNS queries go through AdGuard Home.

Optional: Retain WAN IP (MAC Spoofing)

If you’re migrating from another device and want to retain the same WAN IP from your ISP:

  1. Obtain the MAC address from your previous device
  2. Navigate to Interfaces → WAN
  3. Enter the MAC address in the MAC address field
  4. Save and restart the device

Your new device should now receive the same IP address from your ISP.

Interfaces → WAN should look like this:

MAC Spoofing Image

Optional: Network Time Protocol (NTP)

Since I will be setting up additional security measures such as SIEM and log correlation in the future, it will require proper time synchronization. This section ensures that all devices sync time with OPNsense.

Configuring NTP Server

Navigate to Services → Network Time → General and configure:

  • Select all interfaces (LAN, VLANs)
  • Add upstream NTP servers (Pick an option from pool.ntp.org that is in your region)

Force All Devices to Use OPNsense NTP

Create NAT redirect:

Navigate to Firewall → NAT → Port Forward

FieldValue
InterfaceVPN_OUT_IG
ProtocolUDP
SourceVPN_OUT_IG net
Destination! This Firewall
Dest Port123 (NTP)
Redirect to10.0.0.1:123

This transparently redirects all NTP traffic to OPNsense, ensuring consistent time across all devices.

Configure DHCP NTP option:

Navigate to Services → DNSmasq DNS & DHCP → DHCP Options

Add DHCP option:

dhcp-option=42,10.0.0.1

This tells DHCP clients to use 10.0.0.1 (OPNsense) as their NTP server.

MAC Spoofing Image

Once all the steps are complete, restart OPNsense. Your setup now has:

What’s Working:

  • VPN routing for all traffic with automatic failover
  • DNS filtering with AdGuard (all networks except VLAN46)
  • Network segmentation and firewall rules
  • Kill switch preventing WAN leaks
  • NTP synchronization
  • No DNS, WebRTC or ISP leaks

What’s NOT Working Yet:

  • VLAN traffic (requires switch configuration)
  • Wireless VLAN access (requires AP configuration)
  • DMZ external access (needs testing after switch setup)

Still Required:

  1. Configure managed switches with VLAN tagging
  2. Configure wireless access points for VLAN SSIDs
  3. Test and verify the complete setup

Detailed verification and testing procedures are covered in separate guides linked below.


Next Steps

Now that OPNsense setup is complete, you still need to configure switches, wireless access points and then verify that everything works.

Since the length of this post is already quite large, I will split up the next steps as follows:

1. Configure Network Switches & WiFi Access Points [REQUIRED]

VLANs won’t work until we configure switches with VLAN tagging. You will need a managed switch for this as “dumb” switches don’t have this capability. You can read more about the standard: IEEE 802.1Q.

Guide: VLAN Switch Configuration for OPNsense

2. Test Your Setup

Once you’ve completed this setup and configured your network switches, verify that everything is working correctly.

This separate post focuses on verifying DNS resolution, VPN tunnels, firewall rules and VLAN isolation.

Guide: Testing Your OPNsense Setup

3. Configure Plex (Optional)

Plex-specific verification steps for testing and configuring Plex remote access that bypasses VPN routing.

Guide: Plex Remote Access Through OPNsense VPN

4. Local Domain Setup with Reverse Proxy (Optional)

Setting up local domains (e.g., service.home.byteron.com) with a reverse proxy for accessing your self-hosted services.

Guide: Local Domain Setup with Reverse Proxy

Future Topics

In the near future I plan to document:

  • Advanced security tools: Zenarmor, CrowdSec, Suricata, Wazuh
  • Monitoring and alerting

Leave a comment if there is anything else you’d like me to cover in the future.

[changelog]
2025-10-20:
  • Initial publication

Disclaimer

Use the information provided here at your own risk, but if you find errors or issues in this guide, leave a comment and I’ll try to address them ASAP.

RO

Ronaldo

BSc Mathematics | MSc Information Security

  • Technical problem-solver with unusually broad capabilities & interests
  • I figure things out and get things done

Comments

Table of Contents