Might be bold to call this a guide, it’s rather my personal documentation rewritten to a quite extensive How to get started with OPNsense and VLANs. Modify for your own needs and preferences - I’ve just started and probably lack some understanding.

OPNsense dashboard

Install OPNsense

  • Read the official documentation
  • Download the latest image and create a bootable USB with preferable tools, eg. tar and dd.
    • tar -xvjf OPNsense-24.7-vga-amd64.img.bz2
    • dd if=OPNsense-24.7-vga-amd64.img of=/dev/sdX bs=16k
  • Let the initial setup run, it will auto-select WAN/LAN interfaces if you’ve plugged them in - running live in memory.
  • Login as user installer password opnsense to start the actual installer.
    • Install on ZFS (my preferred, if running virtualized with ZFS behind just choose UFS)
    • Pick your drive next. After that, go with default swap (8Gb) and then accept.
    • When it’s done, choose a new password and then reboot.

After it rebooted, access its webGUI on the internal IP it shows (probably 192.168.1.1) and start the initial setup wizard.

General

  • Hostname: OPNsense
  • Domain: home.arpa
  • TimeZone: Europe/Stockholm
  • DNS Servers: 9.9.9.9 and 149.112.112.112 (my preferred upstream DNS - quad9)
    • Uncheck Override DNS to not let ISP DNS override yours
  • Unbound DNS
    • Enable Resolver: check
    • Enable DNSSEC Support: check

WAN: Select Type (usually DHCP) and there’s nothing more to configure, if you’re on Static IP or something else, just enter the options needed.

LAN: Same thing but for the internal network, pick your internal address here - depending on your layout with VLANs this might be used as the VLAN1 (default LAN) subnet.

Complete the initial configuration - then check for updates @System/Firmware/Updates (Go to Status -> Check for Updates) and reboot if required.

And now the most important step! DARK THEME!

  • Go to @System/Firmware/Plugins and filter for theme.
  • Install a theme (cicada, rebellion, vicuna are all dark themes).
  • Apply @System/Settings/General/Theme -> Save.

Now you should be up and running with the initial configuration done, let’s dive deeper!

VLANs

I’ll create a setup with 4 VLANs;

  • 10.0.10.0 Home, for all default clients, PCs, phones.
  • 10.0.20.0 Management, for internal servers and services.
  • 10.0.30.0 DMZ, for servers and services accessible from the internet.
  • 10.0.40.0 Guest, for my guest Wifi.

Creating a VLAN interface and DHCP.

Example setup for VLAN 10 - Home:

  • @Interfaces/Other Types/VLAN
  • Add
    • Name: vlan0.10
    • Interface: LAN
    • Tag: 10
    • Description: 10_home
  • @Interfaces/Assignments/Assign a new interface
    • Choose vlan0.10 -interface (virtual device)
    • Choose a short+descriptive description eg 10_home (default will be opt1)
    • Add
    • New interface added, eg 10_home - click to edit
      • Enable Interface: check
      • IPv4 Configuration Type: Static IPv4
      • IPv4 address: 10.0.10.1/24 (this is the routers IP-address on this network)
      • Save!
  • @Services/DHCPv4/10_home
    • Enable: check
    • Range: 10.0.10.100 - 10.0.10.200
    • Gateway: 10.0.10.1
    • DNS: leave empty for system default or set specific for VLAN
    • DHCP Static Mappings
      • Add MAC + IP of preferred static lease (or do this after each client have connected).

Repeat with any other VLAN you wish to have - adjust the IP’s and names per your layout.

Firewalling VLANs

Good read: homenetworkguy’s cheat-sheet

Rules are usually created on the originating interface - so to allow traffic eg. from VLAN10 to a something on VLAN20, the rule is created on the VLAN10 interface context. Rules are read from top to bottom - so if you’ve got an “allow all” rule, any block rules have to be above that. Vice versa.

OPNsense will by default set up a Default allow LAN to any rule on the LAN-interface, to allow clients on the LAN-network (192.168.1.0/24 if you’ve not changed it) to reach any other. This is fine for now, and you’d might want to set up a equal rule on your first VLAN while learning and setting up the rest.

Create an allow to ANY rule on the VLAN10 - home interface:

  • @Firewall/Rules/10_home
  • Add
    • Action: Pass
    • Interface: 10_home
    • TCP/IP Version: IPv4+IPv6
    • Protocol: any
    • Source: 10_home net
    • Direction: in (this means entering this interface)
    • Destination: any (to allow 10_home to ANY other network)
    • Destination port: any
    • Description: allow to ANY
  • Save

This is fine for the internal networks for now. I suggest to lock it down further later.

Securing it further can be done by either adding restrictions above the “allow to ANY” rule (like block traffic from 20_mgmt to 10_home) this might be fine in a default home setup, implicit allow - things not explicitly blocked will be allowed. Preferrably though a more secure way is implicit deny - anything not explicitly allo::wed will be blocked and I’ll show that with the DMZ-VLAN.

Create a RFC1918 (private networks) alias

We’ll create an alias to use in our rules, to not have to create separate rules for every network.

  • @Firewall/Aliases -> Add
    • Name: RFC1918
    • Type: Network(s)
    • Content: 192.168.0.0/16 100.64.0.0./10 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12
    • Description: Group of Private Networks

Create rules on the DMZ interface to only allow traffic to/from internet.

I only run IPv4, so I wont create any IPv6 rules.

Allow HTTP+HTTPS to internet (non-private networks)

  • @Firewall/Rules/30_dmz
  • Add
    • Action: Pass
    • Interface: 30_dmz
    • Protocol: TCP
    • Source: any
    • Destination / Invert: check (to invert the match)
    • Destination: RFC1918
    • Destination port range: HTTP-HTTP
    • Description: Allow HTTP to non-private network
  • Save

Clone this rule, then edit and change Destination port range to HTTPS-HTTPS and edit the description.

Allow DNS to internal network

  • @Firewall/Rules/30_dmz
  • Add
    • Action: Pass
    • Interface: 30_dmz
    • Protocol: TCP/UDP
    • Source: any
    • Destination: 30_dmz net
    • Destination port: DNS-DNS
    • Description: Allow to internal DNS
  • Save

That’s it. The machines in the DMZ-network will reach internet and local dns but nothing else.

What if we’d like a specific machine on the DMZ (reverse proxy?) to reach a web service on the management network?

Example rule to allow specific DMZ-machine to specific Mgmt-service

  • @Firewall/Rules/30_dmz
  • Add
    • Action: Pass
    • Interface: 30_dmz
    • Protocol: TCP
    • Source: 10.0.30.111
    • Destination: 10.0.20.222
    • Destination port: 8080
    • Description: Allow proxy to internal Mgmt web
  • Save

Now to allow this to be reached from the internet we need to do some NAT.

Port Forwarding from Internet to DMZ Reverse Proxy

So let’s assume we’ve got a reverse proxy listening on port 80+443 at the IP 10.0.30.111.

  • @Firewall/NAT/Port Forward
  • Add
    • Interface WAN
    • Protocol: TCP
    • Destination: WAN address (this means traffic entering the firewall on the WAN address)
    • Destination port range: HTTP-HTTP
    • Redirect target IP: Single host or Network, 10.0.30.111
    • Redirect target port: HTTP
    • Description: HTTP to DMZ
    • Filter rule association: Add associated filter rule (this will automatically create rules on the WAN interface to allow the traffic)
  • Save

Clone this and edit to change HTTP to HTTPS.

Then check @Firewall/Rules/WAN to see the rules “HTTP(S) to DMZ” added. If you chose None at Filter rule association or if they’re missing add them manually.

Unbound DNS

Unbound is a validating, recursive, caching DNS resolver.

Unbound will allow us to block unwanted dns queries, make queries faster by caching and being a recursive DNS, more secure by DNS over TLS and also allow us to set up internal overrides/rewrites.

@Services/Unbound DNS First make sure it’s enabled @./General -> Enable Unbound.

  • Enable DoT @./DNS over TLS
    • Add
      • Server IP: 9.9.9.9
      • Verify CN: dns.quad9.net
    • Add (secondary)
      • Server IP: 149.112.112.112
      • Verify CN: dns.quad9.net
    • Apply
  • Enable Blocklists @./Blocklist
    • Enable: check
    • Type of DNSBL: Toggle preferred lists
      • Start with some Abuse.ch, OISD, AdAway, AdGuard, Blocklist.site and then add more for testing.
    • Whitelist Domains: Here you can whitelist specific domains if you find them blocked.
  • Set local rewrites @./Overrides
    • Add
      • Host: dash
      • Domain: home.arpa
      • IP address: 10.0.30.100 (internal reverse proxy, as an example)
    • Apply (this will now rewrite dash.home.arpa to 10.0.30.100)

Check Register DHCP Static Mappings @Unbound DNS/General to automatically map static DHCP-client names to be resolved by the DNS.
Example: a client named stormy will be reachable by stormy.home.arpa or even just stormy when the @System/Settings/General/DNS search domain is set to home.arpa.

If you’ve already got an internal service like AdGuard Home or PiHole, you could forward your queries to that instance.

  • @./Query Forwarding
  • Add
    • Server IP: 10.0.30.100 (as an example)
    • Server Port: 53 (or 853 if the internal service uses DoT)
  • Apply

Add Cron job to update Unbound Blocklists.

  • @System/Settings/Cron
  • Add
    • Set Minutes,Hours,DotM,Months,DotW to 0,0,*,*,* to run at 00:00 every day.
    • Command: Update Unbound DNSBLs
    • Description: Update Unbound BLs

Wireguard

WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.

Wireguard will create an VPN tunnel to reach our home network from remote locations This will require a public domain or static IP or similar.

First create an instance:

  • @VPN/WireGuard/Settings

  • Add

    • Name: WGMain
    • Generate keypair
    • Listen port: 51820
    • Tunnel address: 10.10.0.1 (non-existing within internal networks)
  • Save

  • @./Peer Generator:

    • Instance: WGMain
    • Endpoint: -Public Domain/IP here-
    • Name: Phone1
    • Address: 10.10.0.2/32
    • Generate Preshared Key
    • DNS Servers: -Internal DNS here-
    • QR-scan and/or copy+paste config!
    • Store and generate next

Set up the wireguard interface:

  • @Interfaces/Assignment/ Assign a new interface
    • Pick wg0 (the newly created WireGuard instance)
    • Description: WG_admin
    • Add, Save
    • Click the new interface WG_admin and Enable Interface
    • Save + Apply

Allow Wireguard on WAN

  • @Firewall/Rules/WAN - Add
    • Pass
    • Interface: WAN
    • Protocol: UDP
    • Destination: WAN adress
    • Destination port range: other, 51820

Enable outbound internet access through Wireguard

  • @Firewall/NAT/Outbound
    • Mode: Hybrid outbound NAT rule generation
      • Save
      • Apply
    • Manual Rules - Add
      • Interface: WAN
      • Source address: WG_dmz net
      • Save
    • Apply

Allow wireguard client to connect to any internal VLAN

  • @Firewall/Rules/WG_admin
    • Pass
    • Interface: WG_admin
  • Protocol: any
    • Source: WG_admin net
    • Destination: any

To ensure traffic is not fragmented and errors out, create a normalization rule:

  • @Firewall/Settings/Normalization
    • Add
      • Interface: WireGuard (Group)
      • Max mss: 1380 (or 1360 if you use IPv6)

I still need to tinker some with this setup - read more in the official docs

Spamhous DROP-list official docs

DROP Don't Route or Peer -lists

@Firewall/Aliases -> Add a new alias:

Name spamhaus_drop
Description Spamhaus DROP
Type URL Table (IPs)
Content https://www.spamhaus.org/drop/drop.txt
Refresh Frequency Days:1

Save and apply. Use the list in a block rule:

  • @Firewall/Rules/WAN
  • Add
    • Block
    • Interface: WAN
    • Direction: in
    • Source: spamhaus_drop
    • Destination: any
    • Category: Spamhaus
    • Description: Spamhaus DROP
  • Save
  • Add
    • Block
    • Interface: WAN
    • Direction: out (This is usually not preferred, but instead of setting up on every other VLAN)
    • Source: any
    • Destination: spamhaus_drop
    • Category: Spamhaus
    • Description: Spamhaus DROP
  • Save
  • Apply

Dynamic DNS - DDNS

If you’ve got a dynamic IP and want your domain to always point to it, you’ll need DDNS. I use Loopia as my domain provider.

Install the plugin:

  • @System/Firmware/Plugins/os-ddclient
    • Install

Configure the plugin:

  • @Services/Dynamic DNS/Settings
  • Add
    • Service: -your provider- (loopia in my case)
    • Username: -username-
    • Password: -password-
    • Check ip method: (loopia)
    • Interface to monitor: WAN
    • Force SSL: check
  • Save Wait for it to update, or restart the service.

Crowdsec - Intrusion Detection

Crowdsec is an open-source, lightweight software, detecting peers with aggressive behaviors to prevent them from accessing your systems. read more

Register account at https://crowdsec.net Back on opnsense: Install plugin @opnsense/system/firwmare/plugin -> Crowdsec

  • @Services/Crowdsec/Settings
    • Enable Log Processor: check
    • Enable LAPI: check
    • Enable Remediation Component: check
    • LAPI listen address: 127.0.0.1
    • LAPI listen port: 8080
    • Enable log for rules: check
    • Tag for matched packets: crowdsec
  • @crowdsec.net web management
    • Security Engines/Connect my Security Engine now
    • Copy command cscli console enroll ABC123
  • @opnsense - SSH to opnsens (enable SSH @System/Settings/Administration -> Enable Secure Shell + Permit root user login)
    • Make sure Crowdsec is up service crowdsec start
    • Run the command above cscli console enroll ABC123
    • Reload Crowdsec service crowdsec reload
  • @crowdsec.net web management - Accept enroll
  • @opnsense SSH - whitelist private addresses
    • cscli parsers install crowdsecurity/whitelists
    • cscli collections install crowdsecurity/opnsense
    • cscli collections install crowdsecurity/opnsense-gui
    • Reload Crowdsec service crowdsec reload

Check that firewall rules are added @Firewall/Rules/WAN -> Expand Automatically generated rules.

  • $crowdsec_blacklists
  • $crowdsec6_blacklists

Alerts and Blocks will be shown @Services/CrowdSec/Overview -> Alerts/Decisions

Add Cron job to update Unbound Blocklists.

  • @System/Settings/Cron
  • Add
    • Set Minutes,Hours,DotM,Months,DotW to 0,3,*,*,* to run at 03:00 every day.
    • Command: Update and reload intrusion detection rules
    • Description: ids rule updates

NetFlow + Insight

NetFlow provides valuable information about network users and applications, peak usage times, and traffic routing. Insight offers a full set of analysis tools, ranging from a graphical overview to a csv exporter for further analysis with your favorite spreadsheet.

  • @Reporting/NetFlow
    • Listening interfaces: 10_home, 20_mgmt, 30_dmz, WAN
    • WAN interfaces: WAN
    • Capture local: check
    • Version: v9
    • Destinations: 127.0.0.1:2056

Then at @Reporting/Insight you can view your NetFlow charts and statistics.


That’s it - way more than I thought it’d be when I started writing. I’ll add more when I dive deeper.