Segmented a flat home network behind an OPNsense firewall — three VLANs, 802.1Q trunking over a single NIC, per-VLAN DHCP and DNS. Nobody else in the house noticed it happen.
My home network ran flat for years — Proxmox, Docker, and Jellyfin sharing one broadcast domain with every phone and TV in the house. Phase 1 fixes that: a real firewall, VLAN-tagged networks, and rules that enforce what can reach what.
Replacing the household router would have meant reconfiguring every device in the house. Instead I put OPNsense behind it as a Proxmox VM, leaving the TP-Link to handle NAT, DHCP, and Wi-Fi as before. With one NIC on the host, every VLAN rides a single 802.1Q trunk to a managed switch. Tagging happens at the Proxmox bridge, not inside any guest OS. Three VLANs: MGMT, SERVERS, and a reserved IOT — with SERVERS and IOT blocked from MGMT.
Deployed OPNsense as a Proxmox VM with four virtual NICs — one untagged WAN link and three VLAN-tagged interfaces for MGMT, SERVERS, and IOT.
Configured a single 802.1Q trunk between Proxmox and a Netgear GS108Ev4 managed switch — no physical NIC upgrade required.
Wrote firewall rules enforcing that SERVERS and IOT cannot reach the MGMT network, while MGMT retains full administrative access.
Migrated a test VM onto the new SERVERS VLAN as a zero-downtime proof of concept, with household Wi-Fi and Jellyfin left completely untouched.
Configured Unbound as a fully recursive DNS resolver and Kea for per-VLAN DHCP, including host reservations.
Moved Proxmox host management onto the MGMT VLAN, routing through OPNsense rather than the household router.
Chose Tailscale over a port-forwarded WireGuard tunnel for remote access — it only needs an outbound connection to its coordination servers, so no inbound ports are opened on the household router at all.
Challenges
Locked myself out of the switch's management page by changing an access port's PVID to a VLAN with no route back. Factory reset to recover — and a lesson about changing one variable at a time.
DHCP silently failed on every VLAN after deploying OPNsense. The Kea logs traced it to Dnsmasq, still running from the initial console setup and holding port 67 on every interface Kea needed.
A Kea DHCP reservation showed correctly in the GUI but was never honored. The client kept requesting INIT-REBOOT reconfirmation of its old lease, and Kea granted it without checking the reservation. A subnet-ID association bug, not a client issue. Fell back to a static IP rather than block the build.
One switch port refused to hand out DHCP while every other port worked. I eliminated cabling, VLAN membership, PVID, and port config, and narrowed it as far as the switch's Loop Prevention feature without ever landing a definitive root cause. It is documented and still open rather than quietly dropped.
What I Learned
Segmentation was as much change management as configuration. Configuring VLANs was easy; doing it without breaking anyone's Wi-Fi was the real constraint. The troubleshooting taught me more. A service that gives up after exhausting retries looks identical to a healthy one in the logs. Only the live Kea log showed the difference.
Next Steps
Migrate Jellyfin behind the firewall with a scoped exception rule, or accept manual server addressing in clients.
Populate the IOT VLAN with real devices.
Root-cause the Kea reservation / INIT-REBOOT matching bug instead of working around it.