Network
A flat home network with OPNsense handling routing and firewalling. Everything sits on a single LAN — there is no VLAN segmentation today, which keeps the setup simple for a trusted home environment (the trade-off is discussed under Architecture decisions).
Topology
graph TD
internet((Internet)) --> modem[Cable modem]
modem --> opnsense[OPNsense VM<br/>router · firewall · DHCP · DNS]
opnsense --> ap[TP-Link AP<br/>Wi-Fi]
opnsense --> proxmox[Proxmox<br/>hypervisor]
opnsense --> monitoring[monitoring<br/>Prometheus · Grafana · Loki]
opnsense --> kontti[kontti<br/>media · AI · apps]
opnsense --> nas[Synology DS920+<br/>NFS · backup target]
opnsense --> ha[Home Assistant]
tailscale[/Tailscale subnet router/] -.->|VPN| opnsense
kontti -.->|NFS| nas
monitoring -.->|scrape| kontti
monitoring -.->|scrape| proxmox OPNsense runs as a VM on Proxmox, so the router itself is part of the virtualised platform it protects — see the Proxmox and OPNsense pages.
Devices
| Device | Role |
|---|---|
| OPNsense (Proxmox VM) | Router, firewall, DHCP, DNS |
| TP-Link AP | Wi-Fi access point |
| Synology DS920+ | Network storage |
| Proxmox | Hypervisor |
| monitoring | Monitoring server (Lenovo laptop) |
| kontti | Application and media server |
| Home Assistant | Smart home controller |
| Cable modem | ISP uplink |
Remote access is handled by Tailscale running as a subnet router on OPNsense.
Addressing and firewall design
The LAN is a single private subnet, and addresses split into two groups by intent:
- Infrastructure and fixed devices — Proxmox, both uCore servers, the NAS, Home Assistant, and the IoT and ESPHome devices — get static DHCP reservations keyed to MAC address. Every one keeps a stable address that DNS host overrides, Prometheus scrape targets, Wake-on-LAN and Home Assistant's device integrations all depend on, without any of it being hard-coded on the machines themselves. The reservations live in one place (dnsmasq on OPNsense), so no address is pinned on the machines themselves.
- Transient clients — phones and laptops — take dynamic leases from the general pool.
The firewall posture is deliberately minimal, because the attack surface is small by design. Nothing is port-forwarded from the internet: the WAN side is default-deny inbound, and the only way in is Tailscale — an encrypted tunnel rather than an open port. Internal services aren't exposed directly either; they sit behind HAProxy, which terminates TLS and is the single reachable front door for the web UIs, so individual service ports never need a firewall hole of their own.
How OPNsense itself is managed
The router used to be the one thing here that was configured only through its own web interface. Since September 2026 most of it is declared in the same proxmox repo as the hypervisor, driven over OPNsense's REST API rather than through the GUI or by editing its XML:
| Playbook | What it declares |
|---|---|
opnsense_dns.yml | 29 Unbound host overrides, plus the two forwards that hand tielinen.me and the reverse zone to dnsmasq |
opnsense_haproxy.yml | 26 published services, each as four linked objects — real server, host-header condition, backend pool, routing rule |
opnsense_dhcp.yml | 17 DHCP reservations, the dynamic pool, and the DHCP option that hands every client the filtering resolver |
opnsense_shaper.yml | The traffic shaper: 2 bandwidth pipes, 6 weighted queues, 10 rules |
What stays in the GUI is a short and deliberate list: the HTTPS front end itself, the ACME certificate chain, firewall rules, interface assignment, and the Tailscale subnet router. The reasoning for the first two is on the OPNsense page.
Deletion is explicit rather than implied: none of these playbooks purge an entry just because it has dropped off a list, so an object disappears only when it is named in a separate absent list. With 29 host overrides fronting the reverse proxy, one forgotten row in a purge-on-absence model would break name resolution for the lot, so the possibility of drift is accepted in exchange.
What that leaves is a router most of which now has a reviewable definition — a diff to read before a change, and a list that can be read without logging in. The rest is still recovered rather than rebuilt, and that path runs at two levels:
- The whole VM, via the nightly Proxmox vzdump job. This is the fast path, and it's the one that has actually been exercised — the restore test has recovered the OPNsense guest into a throwaway VMID with the network detached, so the path is proven rather than assumed.
- The configuration on its own, exported from OPNsense to the Synology over SFTP. This is the layer that survives the VM being unrecoverable: a fresh OPNsense install plus that file reproduces the router without depending on the hypervisor's backup at all. It lands on the same NAS share, so it inherits the off-site replication to Backblaze along with everything else.
Two independent recovery paths at different granularities, which is the right shape when the thing being protected is the device every other device depends on — and the parts that are declared now rebuild from the repo on top of them.
The flat LAN
The honest limit of this design is the flat LAN itself. With no VLANs, firewall rules can't put a boundary between a compromised IoT device and the servers — they share one broadcast domain. That is the known, deliberate gap that would justify a managed switch and inter-VLAN rules if the threat model grew.
My experience
A flat network has been sufficient for my needs. The TP-Link AP covers Wi-Fi well enough for a small apartment. I prefer wired connections but haven't bothered running cables through the flat.
OPNsense used to be the one thing here I didn't manage with Ansible, because I hadn't found a collection for it I'd want to depend on. When I found one that looked good, I moved most of the router into the repo. Keeping settings as code documents them at the same time, which is most of why I wanted it — the DNS and HAProxy configuration is a lot clearer as a list in a file than spread across menus I have to remember to open.
Moving it there turned up a few stale settings and DHCP reservations I'd forgotten about, which was the useful part. Otherwise there wasn't much drama. The HTTPS front end and the ACME chain are still done by hand; I don't need to touch them often, and I may take them into Ansible too if the collection develops.
The Synology DS920+ has worked well as an NFS server and as a backup target that replicates to Backblaze B2. At some point I'll likely replace it with a different solution.