Moving smart plugs, bulbs, TVs and cameras onto their own VLAN is the single biggest security win on a home network, and it is also the change most people roll back a week later because the cast button went grey and Home Assistant lost half its devices. The usual "fix" is an allow-any rule from IoT back into the house, which quietly undoes the whole point.
This is the setup I use on OPNsense at home: an IoT zone with a three-rule firewall policy, plus the handful of narrow exceptions that keep discovery, casting and Home Assistant working. No allow-any, no guesswork. Addresses below are examples; use your own.
The zone plan
A flat home network has one firewall rule that matters, the one between the house and the internet. Every interior door stands open. Zones fix that: group devices by how much you trust them and let new connections flow only from more trusted to less trusted.
| Zone | VLAN | Subnet | What lives there |
|---|---|---|---|
| Trusted | 10 | 192.168.10.0/24 | Your own laptops, desktops, phones |
| Servers | 20 | 192.168.20.0/24 | Proxmox guests, NAS, Home Assistant |
| IoT | 30 | 192.168.30.0/24 | Plugs, bulbs, cameras, TVs, speakers |
| Guest | 40 | 192.168.40.0/24 | Visitors' devices |
| Management | 99 | 192.168.99.0/24 | Firewall, switch, AP admin, Proxmox hosts |
The third octet matches the VLAN ID on purpose: when 192.168.30.57 shows up in a log you know it is an IoT device without looking anything up.
The one sentence behind every rule: a zone may start connections to zones below it, never to zones above it. Replies always flow back, because OPNsense is stateful. Once it lets a connection out, it tracks it and lets the answers in without a separate rule. That one fact is why Home Assistant can control IoT devices while IoT devices cannot reach Home Assistant.
A sorting rule of thumb: if you did not install its software, it is IoT. TVs, streaming sticks, consoles, speakers, printers, robot vacuums, anything with a manufacturer's app.
How OPNsense reads rules
Three facts explain almost every surprise:
- Rules apply where traffic enters. To control what IoT devices can start, you write rules on the IOT interface tab, not on the tab of the network they are trying to reach.
- First match wins. Rules are read top to bottom and the first match decides. Order is the rule set.
- Anything unmatched is blocked. The built-in default deny catches whatever no rule allowed, so you only write what you want to allow, plus explicit blocks where you want a log entry.
Aliases first
Write the names before the rules. Firewall → Aliases:
| Alias | Type | Content |
|---|---|---|
| PRIVATE_NETS | Network(s) | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 |
| PRIVATE_NETS6 | Network(s) | fc00::/7, fe80::/10 |
| HOME_ASSISTANT | Host(s) | 192.168.20.10 |
| FW_SERVICES | Port(s) | 53, 123 |
If your provider gives you IPv6, every rule needs to cover it too (TCP/IP version IPv4+IPv6), and PRIVATE_NETS only knows IPv4, which is why PRIVATE_NETS6 exists. A perfect IPv4 rule set with an open IPv6 path is not a rule set.
The IoT rule set
Every zone gets the same shape: allow what it needs from the firewall, allow the named exceptions, block everything private, allow the internet. On the IOT tab, top to bottom:
| # | Action | Source | Destination | Port | Why |
|---|---|---|---|---|---|
| 1 | Pass | IOT net | IOT address | 53, 123 (FW_SERVICES) | DNS and time from the firewall |
| 2 | Block | IOT net | PRIVATE_NETS | any | Nothing in the house. Tick Log |
| 3 | Pass | IOT net | any | any | Internet |
DHCP needs no rule; OPNsense allows it automatically on any interface where the DHCP service runs.
Rule 2 is the one doing the work. It stops IoT devices from starting connections to your laptops, your NAS and the firewall's web interface. Home Assistant still controls them, because Home Assistant starts those connections from the servers network and the replies come back through the state table.
Log the block rule for the first week. Firewall → Log Files → Live View will show you, within a day, which devices try to reach things they should not, and which legitimate thing you forgot. Then those become narrow exceptions (next section), not a loosened block.
Some devices should not reach the internet at all: a camera that records to your own server, a sensor that only talks to Home Assistant. Put their addresses in an alias such as IOT_LOCAL_ONLY and add a block for that alias above rule 3. Many work perfectly without their cloud; some stop responding, which is useful to know before you buy the next one.
The matching rule on the servers side
Home Assistant has to start connections into IoT, so the SERVERS tab gets one named exception, above its own block-private rule:
- Pass HOME_ASSISTANT → IOT net, any port. Description: "HA controls IoT devices".
- Pass SERVERS net → firewall, FW_SERVICES.
- Block SERVERS net → PRIVATE_NETS (a compromised container cannot wander into trusted or management).
- Pass SERVERS net → any (updates).
One host to one zone, not the whole servers network. Give every rule a description that says why, not what: "HA controls IoT devices" beats "pass 192.168.20.10 to IOT net". A rule nobody can explain a year later is a candidate for deletion.
Why the cast button goes grey
The moment IoT moves to its own network, something stops working, and usually it is not the rules. It is discovery: your phone finds the TV by shouting on the local network and waiting for an answer, and those shouts do not cross a router.
| Protocol | Address and port | Used by |
|---|---|---|
| mDNS (Bonjour) | 224.0.0.251, UDP 5353 | AirPlay, Chromecast, HomeKit, printers, ESPHome |
| SSDP (UPnP discovery) | 239.255.255.250, UDP 1900 | Smart TVs, Sonos, DLNA, some consoles |
A router does not forward multicast between subnets. Your phone on TRUSTED asks "is there a TV here?", the TV on IOT never hears it, and the button stays grey. Nothing is blocked; the question never arrives.
Fix 1: the mDNS repeater
System → Firmware → Plugins, install os-mdns-repeater. Then Services → mDNS Repeater: enable it and select exactly the two interfaces it should bridge, TRUSTED and IOT. Do not add GUEST or MGMT.
The repeater only carries discovery packets. It opens no firewall ports and it does not let IoT devices start connections into trusted. Once your phone knows the TV's address, it opens a normal connection to it, which the trusted rules already allow because that is downhill.
Some devices also need to connect back during a session (Chromecast and AirPlay can open connections from the device to the phone). If casting finds the device but fails to play, look in Live View for blocked traffic from that device to the phone, and add one narrow pass rule on IOT for that device to TRUSTED net with the ports you see. One device, one rule.
Fix 2: SSDP with a broadcast relay
For SSDP devices, os-udpbroadcastrelay plays the same role: one instance for UDP port 1900, multicast address 239.255.255.250, between TRUSTED and IOT. Same discipline: only the interfaces that need it, and a narrow rule if a device needs to call back.
Fix 3: give Home Assistant a leg on IoT
Home Assistant discovers ESPHome devices, Chromecasts and HomeKit accessories by listening for multicast. You can make all of that work through repeaters, but there is a simpler option for Home Assistant specifically: add a second network card to its VM, tagged VLAN 30, so it sits directly on the IoT network as well as on servers.
That gives first-hand discovery with no repeater and no rule exceptions. The trade-off is real: Home Assistant now bridges two zones, so it has to be the best-patched machine in the house, with updates on and two-factor login on its admin accounts.
Device by device
- ESPHome devices report to Home Assistant over its API, and Home Assistant starts that connection, so the IoT rules already cover it. Only discovery needs the repeater or the second NIC.
- MQTT devices (Zigbee2MQTT bridges, Tasmota, Shelly in MQTT mode) start the connection to the broker. If the broker is on servers, add one IOT rule: IOT net → the broker's address, port 1883 (or 8883 with TLS). That is an uphill exception, so keep it to the one host and port.
- Printers need mDNS for discovery and IPP (TCP 631) from trusted, which is downhill and already allowed.
- Cameras recording to a server on your own network: the recorder starts the connection (downhill), no exception needed. Put them in IOT_LOCAL_ONLY if they do not need the cloud.
Devices that ignore DHCP
Some devices hardcode a public DNS or time server and ignore what DHCP hands them. Redirect them to the firewall with a port forward on the IOT interface (Firewall → NAT → Port Forward):
Interface: IOT
Protocol: UDP
Source: IOT net
Destination: ! IOT address (anything except the firewall)
Port: 123
Redirect to: 127.0.0.1, port 123
Description: Force IoT time to the firewall
Make sure the firewall's NTP service (Services → Network Time) listens on IOT. The same redirect works for plain DNS on port 53. (Encrypted DNS on 853 and 443 is a different problem; a redirect cannot catch it, and it needs blocks and the browser canaries instead. That is its own post.)
Test it before you trust it
From a laptop joined to the IoT Wi-Fi:
# Should FAIL (firewall web interface):
curl -k -m 5 https://192.168.30.1
# Should FAIL (a server, Home Assistant here):
nc -vz -w 3 192.168.20.10 8123
# Should WORK (internet + DNS):
curl -sI -m 5 https://example.com | head -1
nslookup example.com 192.168.30.1
Then from a trusted device: open the Home Assistant dashboard, cast to the TV, print something. Write the results down. The checks that must fail are the ones that matter: things that should work get noticed quickly when they break, but things that should fail can quietly start succeeding and nobody complains.
Checklist
- Aliases created before rules, IPv6 included
- IOT tab: firewall services, block PRIVATE_NETS (logged), internet
- HOME_ASSISTANT is the only server that can reach IOT net
- mDNS repeater on TRUSTED and IOT only
- Narrow pass rules for call-backs you saw in the log, one device each
- MQTT exception: IOT net to the broker host and port only
- NTP (and DNS) redirected to the firewall on IOT
- Local-only devices in IOT_LOCAL_ONLY and blocked from the internet
- Tests from each zone written down and passing (and failing where they should)
Drafted with AI help. I edited it, and it's based on my own setup.
This post is adapted from Chapters 4 and 5 of my ebook Lock Down Your Home Network: VLANs, Firewall Rules, WireGuard and DNS Filtering with OPNsense. The free sample is the full firewall-rules chapter; the whole book adds the VLAN build from the wire up, WireGuard remote access, DNS filtering that encrypted DNS can't dodge, and a change routine that keeps you from locking yourself out.


Top comments (0)