Open Source Home Network

The Foundation

Your ISP gave you a router. It works for your ISP.

This is the stack that works for you instead.

Why Replace Your ISP Router?

  • Your ISP router reports back to your ISP
    • Connected devices, usage patterns, DNS queries — all visible upstream by design
    • Firmware you didn't choose, can't audit, and can't update on your own schedule
    • Remote management ports your ISP can reach — and anyone who finds them can too
  • Consumer routers are built to a price point, not a security standard
    • Security patches stop after 12–18 months — your router runs unpatched for years after that
    • No real VLAN support, no per-device firewall rules, no traffic visibility or logging
    • "Guest WiFi" that doesn't actually isolate the guest from your LAN in practice
  • Open source gives you the actual controls
    • You choose what runs, what updates, what phones home — the answer to the last one is nothing
    • Real VLAN segmentation: IoT devices cannot reach your NAS, full stop
    • Per-device firewall rules, traffic shaping, DNS control, VPN, IDS — all in one box you own
  • The two-device stack: OpenWRT + OPNsense
    • OpenWRT: the access layer — manages WiFi radios, SSIDs, and VLANs at the AP
    • OPNsense: the routing and firewall layer — all policy, all security lives here
    • Split by function: easier to reason about, easier to upgrade each independently
  • Cost is lower than you think
    • OpenWRT AP: GL.iNet travel router (~$40–80) or flash your existing router for free
    • OPNsense box: old mini PC + dual NIC (~$50–100) or Protectli Vault (~$200)
    • Less than one year of a "security" subscription router that does a fraction of this
  • What you get that your ISP router cannot provide
    • Full visibility into every device, every connection, every DNS query on your network
    • Firewall rules your IoT lightbulbs can't bypass, no matter what firmware they run

The Two-Device Architecture

[ Devices — phones, laptops, IoT, guests ]
         |
         |  WiFi / Ethernet
         ▼
  ┌─────────────────────────────────────┐
  │         OpenWRT Access Point        │
  │                                     │
  │  SSID: Home    → VLAN 10            │
  │  SSID: IoT     → VLAN 20            │
  │  SSID: Guest   → VLAN 30 (OpenNDS)  │
  │  SSID: 🎯emoji → VLAN 40 (rotating) │
  └──────────────┬──────────────────────┘
                 │  Tagged trunk (all VLANs on one cable)
                 │  "Router on a stick"
                 ▼
  ┌─────────────────────────────────────┐
  │           OPNsense Router           │
  │                                     │
  │  ├── VLAN interfaces + DHCP pools   │
  │  ├── Firewall rules per VLAN        │
  │  ├── Unbound DNS (per-VLAN views)   │
  │  ├── Caddy reverse proxy            │
  │  ├── WireGuard VPN gateway          │
  │  ├── Suricata IDS/IPS               │
  │  └── ntopng traffic monitor         │
  └──────────────┬──────────────────────┘
                 │
                 ▼
         [ Your ISP / Internet ]
  • One cable carries all VLANs between OpenWRT and OPNsense — 802.1Q tagged trunk
  • OPNsense owns all routing decisions: inter-VLAN traffic must pass through it
  • IoT VLAN cannot reach Home VLAN unless OPNsense explicitly allows it — it doesn't

OpenWRT — DSA VLAN Segmentation

Source: blog.holtzweb.com/posts/openwrt-dsa-networking-vlans-with-opnsense/

What DSA Is and Why It Matters

  • Distributed Switch Architecture — the new OpenWRT switch management model (OpenWRT 21+)
  • Replaces the old switch section in LuCI — older guides showing a "Switch" tab are outdated
  • Router on a stick: OpenWRT is the AP, OPNsense is the router — one cable carries all VLANs tagged
  • OPNsense defines the VLANs, IP ranges, and DHCP pools — OpenWRT just relays them upstream

Setting It Up in OpenWRT (DSA)

  • Create a bridge device — select the interface plugged into OPNsense as a member
  • Enable Bridge VLAN filtering on that bridge
  • Bridge VLAN filtering tab: set VLAN IDs, mark uplink port as tagged, device-facing ports as untagged
  • Create a separate interface per VLAN using DHCP client mode, bound to br-lan.VLANID
  • Assign each WiFi SSID to its corresponding VLAN interface in the wireless config

On the OPNsense Side

  • Interfaces → Other Types → VLAN — create one per VLAN ID on the trunk interface
  • Assign each VLAN interface, enable DHCP server per VLAN with its own subnet and range
  • Firewall rules: default is no inter-VLAN communication — add only what you explicitly need
  • Result: IoT VLAN gets internet, cannot ping or scan anything on Home VLAN — OPNsense enforces this

OpenWRT — SSID Separation and Client Isolation

SSID Layout and Purpose

SSID VLAN Purpose Internet Sees LAN?
Home 10 Trusted devices — phones, laptops, desktops Yes Yes
IoT 20 Smart TVs, bulbs, plugs, printers Yes No
Guest 30 Visitors — hits captive portal first Yes No
Select 40 Rotating credentials — contractors, select access Yes No

Client Isolation — Devices on the Same SSID

  • Without client isolation: two devices on the same SSID can talk to each other directly at L2
  • With client isolation: each device is isolated — they can only reach the router (gateway), not each other
  • OpenWRT: Wireless → Interface → Advanced Settings → Isolate Clients → checked
  • Essential on Guest and IoT: you don't want your Roomba scanning your printer
  • Your Roomba is fully capable of scanning your printer — IoT firmware is not to be trusted

Why This Matters for IoT Specifically

  • IoT devices run old firmware with no update path — one compromised bulb is the beachhead for an attacker
  • VLAN isolation: the compromised device can reach the internet (to phone home) and nothing else on LAN
  • Client isolation: the compromised device can't even see other IoT devices on the same SSID
  • ntopng will show you exactly what your IoT devices are calling — then decide if that's acceptable
  • Band steering: same SSID on 2.4GHz and 5GHz — both tied to same VLAN, IoT devices join 2.4GHz naturally

OpenWRT — Captive Portal with OpenNDS

Source: holtzweb.com/openwrt-presentation.html

What a Captive Portal Does and Why

  • Intercepts all HTTP traffic from new clients and redirects them to a splash page before internet access
  • Client must accept terms, enter a code, or authenticate — same system hotels and coffee shops run
  • Forces acknowledgment of acceptable use, logged with timestamp — useful for events and workshops
  • Session timeouts: guest access expires automatically — you don't have to revoke passwords
  • Bandwidth limits per client: your guests can't saturate your uplink during a workshop

OpenNDS Setup

  • Lightweight, runs directly on OpenWRT — no external server needed: opkg update && opkg install openNDS
  • Bind OpenNDS to your guest VLAN interface only — does not affect other SSIDs
  • gatewayname — appears on the splash page header
  • maxclients — hard cap on simultaneous guest connections (useful for event management)
  • sessiontimeout — auto-expire sessions after N minutes (set 0 for no timeout)
  • downloadlimit / uploadlimit — bandwidth cap per client in kbps

Customization and Advanced Auth

  • Default template lives in /etc/openNDS/htdocs/ — edit splash.html for your logo and terms
  • Add a voucher code field: guests enter today's code, OpenNDS validates before granting access
  • FAS (Forward Authentication Service): hands off auth to an external server
    • Use case: event access codes, time-limited vouchers, integration with booking or ticketing systems
    • External server returns a token; OpenNDS grants or denies based on the response

OpenWRT — Rotating SSID and Password

Source: blog.holtzweb.com/posts/turn-network-services-off-after-business-hours/

The Concept and Use Cases

  • One SSID with credentials that automatically rotate on a schedule — old credentials stop working
  • New SSID name and password generated, applied, and emailed to all authorized recipients automatically
  • Devices not re-added each cycle lose access — useful for contractors, short-term access, events
  • No manual credential distribution: the cron job sends the email, recipients just connect

How the Script Works

  • Downloads a list of candidate SSID names — most begin with an emoji character
  • Extracts password candidates from a Voltaire novel — large natural-language corpus, memorable
  • Filters by configured min/max character length — usable but not trivially short
  • Updates OpenWRT via uci set wireless.@wifi-iface[N].ssid and .key
  • Runs wifi reload to apply changes live — no reboot required
  • Sends summary email via Brevo API: "Today's credentials: 🎯weird-ssid / correcthorsebattery"
  • Swap Brevo for any transactional email API — Mailgun, SendGrid, Postmark all work identically

Why Emoji SSIDs?

  • Makes the network visually distinct in any WiFi list — harder to spoof convincingly
  • Slightly confusing to passive WiFi scanners — which is a feature, not a bug
  • Schedule via cron: daily / weekly / monthly depending on paranoia level
  • Combine with after-hours WiFi shutdown: SSID down entirely outside business hours, rotating when up

OPNsense — Advanced Firewalling

Source: blog.holtzweb.com/tags/opnsense/

Stateful Firewall Fundamentals

  • Consumer router: "allow everything outbound, block unsolicited inbound" — that's the entire ruleset
  • OPNsense: stateful packet inspection on every interface, every direction, full logging per rule
  • Tracks connection state: new, established, related, invalid — rules match on any combination
  • Default policy is configurable per interface — start with deny-all, add allow rules explicitly
  • Floating rules: apply across all interfaces simultaneously — use for global policies and IP blocklists

Inter-VLAN Rules — The Critical Part

  • By default in OPNsense: VLANs cannot communicate with each other — you add only what you need
  • "Home VLAN can reach NAS on port 445" — one explicit rule, everything else is dropped silently
  • IoT VLAN: allow rule for WAN only — it cannot initiate connections to Home, LAN, or other VLANs
  • Drop, not reject: attackers get no information about whether a host exists behind the firewall

Aliases — Named Groups for Clean Rules

  • Instead of one rule per IP: create alias "HomeDevices" containing all trusted IPs
  • Rules reference the alias — add a device to the alias, all associated rules update automatically
  • Aliases can be: IP lists, network ranges, port groups, or URLs (auto-refreshed for blocklists)
  • IP blocklists: one alias pointing at Firehol or Spamhaus, one block rule — 50,000 IPs covered
  • Firewall → Log Files → Live View: watch traffic hit your rules in real time alongside ntopng

OPNsense — Reverse Proxy

Source: blog.holtzweb.com/posts/opnsense-nginx-waf-failover-with-caddy-and-traefik/

What a Reverse Proxy Does in Your Home Network

  • Multiple self-hosted services: Nextcloud, Jellyfin, Home Assistant, Vaultwarden, Gitea
  • One external IP, one port 443 — the proxy routes each request by hostname to the right service
  • Valid HTTPS certificates for all services — no browser certificate warnings, no self-signed exceptions
  • No service listens on a public port directly — only the proxy does, reducing your attack surface

Caddy on OPNsense — the Current Recommendation

  • First-class OPNsense plugin as of 2025 — installed and managed directly from the GUI
  • Automatic HTTPS with Let's Encrypt built in — no separate ACME plugin, no manual cert wiring
  • Simple declarative config: cloud.yourdomain.com → Nextcloud, media.* → Jellyfin, done
  • Feature-rich: reverse proxy, static files, ACME, basic auth, headers, rewrite rules — all in one
  • Actively maintained and responsive to bug reports — support is real and ongoing

A Note on HAProxy

  • HAProxy (os-haProxy) was the go-to OPNsense reverse proxy for years — it works, but the plugin is effectively unmaintained
  • Bugs accumulated with no resolution path; the plugin lags OPNsense releases and WAF integration was painful
  • If you have existing HAProxy configs they will continue to function — but for new setups, use Caddy
  • Full writeup on why the switch happened: blog.holtzweb.com/posts/opnsense-with-haproxy-and-lets-encrypt/

Advanced: Caddy → Nginx WAF → Traefik Failover

  • Caddy sits at the front — terminates TLS, handles ACME, routes to backends
  • Nginx + ModSecurity WAF inspects requests for SQLi, XSS, path traversal before they reach your services
  • Traefik behind the WAF auto-discovers services via Docker labels — zero manual config per new container
  • Failover: if the WAF is unreachable, traffic routes around it — service stays up, WAF failure is not an outage
  • Full stack walkthrough: blog.holtzweb.com/posts/opnsense-nginx-waf-failover-with-caddy-and-traefik/

OPNsense — Traffic Flows and Unbound DNS Views

Per-VLAN Routing and Traffic Shaping

  • Policy-based routing: different VLANs routed through different gateways — per-interface rules
  • IoT VLAN → direct internet (no VPN overhead for your lightbulbs)
  • Home VLAN → WireGuard exit for privacy on all devices automatically
  • Guest VLAN capped at 20Mbps — guests can't saturate your uplink during a workshop
  • IoT VLAN rate-limited — if something is exfiltrating, it's at least doing it slowly
  • Priority queues: VoIP traffic gets low-latency scheduling regardless of concurrent activity

Unbound DNS and Split-Horizon Views

  • OPNsense runs Unbound as DNS resolver — all devices receive it automatically via DHCP
  • Local DNS records: nas.home → your NAS IP, media.home → Jellyfin — no IP memorization needed
  • DNSSEC validation: reject DNS responses without valid signatures — prevents cache poisoning
  • DNS-over-TLS forwarding: encrypt queries to upstream resolver — ISP can't log them at transit
  • Unbound Views (blog.holtzweb.com: Unbound Views in OPNsense to Resolve Domains by VLAN/Subnet):
    • Home VLAN asks cloud.yourdomain.com → gets internal IP (stays on LAN, never leaves router)
    • IoT VLAN asks cloud.yourdomain.com → gets NXDOMAIN (can't even see the service exists)
    • Guest VLAN asks any internal hostname → blocked — guests see only external internet
  • DNSCrypt (blog.holtzweb.com: OPNsense Unbound Multi-site Split-DNS with DNSCrypt):
    • Encrypts and authenticates DNS queries from OPNsense to upstream — prevents ISP interception

OPNsense — WireGuard VPN Gateway

Source: blog.holtzweb.com/tags/opnsense/ — WireGuard Server + Site-to-Site VPN

Remote Access — Your Phone and Laptop Back to Home

  • VPN → WireGuard → Local tab: create the server, assign an IP range for VPN clients
  • Add a peer per client device: each gets its own public key and assigned IP in the VPN range
  • Firewall rule: allow WireGuard interface → Home VLAN — clients on VPN reach everything Home can
  • Client: install WireGuard app, scan QR code from OPNsense — done, connected in under 2 minutes
  • When traveling: all traffic routes through your home OPNsense — your DNS blocklists, your firewall

Site-to-Site — Two Locations, One Network

  • Both OPNsense boxes run a WireGuard instance — each is a peer of the other
  • Each knows the other's public key and endpoint IP — routes configured per subnet
  • "To reach 192.168.2.0/24, use the WireGuard tunnel" — transparent routing between sites
  • Traffic between sites: the internet sees only WireGuard UDP packets on one port

Why WireGuard Over OpenVPN

  • ~4,000 lines of code vs ~100,000 for OpenVPN — dramatically smaller attack surface to audit
  • Modern cryptography: ChaCha20, Poly1305, Curve25519 — faster than OpenVPN's legacy cipher stack
  • Reconnects instantly after network changes: switching WiFi, waking from sleep, no re-auth dance
  • Split tunnel: only traffic destined for home network uses VPN — local internet stays local and fast
  • Kill switch: OPNsense gateway group configuration — block WAN traffic if VPN drops, zero leaks

OPNsense — Suricata IDS/IPS

Deployment and Rule Sets

  • IDS mode: alert and log without blocking — start here for a week before switching to IPS
  • IPS mode: drops matching packets — effective once you've tuned out false positives
  • Enable on WAN (stop inbound threats) and LAN (catch compromised devices calling out)
  • ET Open (Emerging Threats) — free, updated daily, covers malware C2, exploit kits, scanners
  • Abuse.ch Feodo / URLhaus — botnet C2 IPs and domains updated in near-real-time
  • ET Pro — paid, lower false positive rate, more comprehensive coverage for production use

What Suricata Catches That Firewalls Miss

  • Known malware C2 over HTTPS on port 443 — certificate is valid, JA3 fingerprint is flagged
  • DNS exfiltration: data encoded in subdomain strings — a2f3b1.exfil.bad.com — dedicated rules
  • Port scanning originating from inside your LAN — a device is compromised and mapping your network
  • Exploit kit traffic patterns: specific URI structures, headers, response sequencing
  • Cryptocurrency mining pool connections — distinct protocol fingerprints, easy to detect and block

JA3 Fingerprinting — How It Sees Encrypted C2

  • TLS handshakes expose a fingerprint: which cipher suites and extensions the client offers
  • Malware families use the same compiled TLS library → same JA3 hash on every infected machine
  • Suricata: "this HTTPS connection to a valid CDN has a JA3 matching known Emotet behavior"
  • No decryption required — the handshake pattern alone identifies the malware family
  • Tuning: suppress false positives for Steam, game launchers, CDNs — then switch categories to IPS

The Complete Open Source Home Network Stack

INTERNET
    |
    ▼
[ OPNsense WAN ]
    |
    ├── Suricata IDS/IPS          ← inspect all inbound/outbound
    ├── IP Blocklists (Aliases)   ← drop known bad at border
    |
    ▼
[ OPNsense Routing & Policy ]
    ├── VLAN 10 — Home ──────────── Full LAN access
    │     └── Unbound DNS view: internal hostnames resolve
    ├── VLAN 20 — IoT ───────────── Internet only · 10Mbps cap
    │     └── DNS view: internal domains → NXDOMAIN
    ├── VLAN 30 — Guest ─────────── Internet only · captive portal
    │     └── OpenNDS: splash page, bandwidth limit, session timeout
    ├── VLAN 40 — Select ────────── Internet only · rotating SSID
    │     └── Cron: new SSID+PSK daily via Brevo email API
    ├── WireGuard ───────────────── Remote access from anywhere
    ├── Caddy ───────────────────── Self-hosted services + HTTPS
    ├── ntopng ─────────────────── Visibility into everything above
    └── Unbound + blocklists ───── Block before resolve, all VLANs
          |
          ▼
[ OpenWRT Access Point ]
    ├── SSID: Home     → VLAN 10 (trusted)
    ├── SSID: IoT      → VLAN 20 (client isolated)
    ├── SSID: Guest    → VLAN 30 (client isolated + OpenNDS)
    └── SSID: 🎯emoji  → VLAN 40 (rotates daily via cron)
  • No device can see another VLAN without an explicit firewall rule — the default is deny
  • All DNS is filtered for every device on every VLAN simultaneously — no exceptions
  • Malware behavior is detected even over encrypted connections via JA3 fingerprinting
  • You can connect securely from anywhere in the world via WireGuard back to your home network

Resources

Topic Link
DSA VLANs with OPNsense blog.holtzweb.com/posts/openwrt-dsa-networking-vlans-with-opnsense/
OpenWRT captive portal + isolation holtzweb.com/openwrt-presentation.html
Rotating SSID script blog.holtzweb.com/posts/turn-network-services-off-after-business-hours/
All OPNsense posts blog.holtzweb.com/tags/opnsense/
Caddy + Nginx WAF + Traefik blog.holtzweb.com/posts/opnsense-nginx-waf-failover-with-caddy-and-traefik/
WireGuard server + site-to-site blog.holtzweb.com — OPNsense as a WireGuard Server
Unbound DNS Views per VLAN blog.holtzweb.com — Unbound Views in OPNsense
Nginx WAF + Caddy + Traefik failover blog.holtzweb.com — Transparent Nginx WAF Failover
DNSCrypt + Split-DNS blog.holtzweb.com — OPNsense Unbound Multi-site Split-DNS
Geofiltered IP blocklists blog.holtzweb.com — Geofiltered IP Address Blocklists Aggregator
OPNsense docs docs.opnsense.org
OpenWRT hardware table openwrt.org/toh
GL.iNet routers gl-inet.com — OpenWRT pre-installed, travel or home AP
Protectli Vault FW4B/FW6 protectli.com — fanless x86 purpose-built OPNsense appliance