Guide: Setting up OPNsense with VLANs and extras
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.
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
- Add
- 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)
- Add
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
- Set Minutes,Hours,DotM,Months,DotW to
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
- Mode: Hybrid outbound NAT rule generation
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)
- Add
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
- Make sure Crowdsec is up
- @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
- Set Minutes,Hours,DotM,Months,DotW to
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.