Inter-VLAN Firewall Policy (Zone-Based)¶
Status: Design/runbook applied in phases and verified by live UDM scans. For the as-configured policies, zone matrix, and communication matrix scanned from the UDM, use Firewall (live posture). This page is the intended policy narrative and remediation guide. Security register: Closes SEC-002. Requires: UniFi Network 9.x+, gateway firmware 4.1+.
Design Principles¶
- Default deny between zones (ZBF default for custom zones)
- Named policy exceptions for legitimate cross-zone traffic
- Management VLAN has unrestricted access (admin plane)
- IoT and Security VLANs are fully isolated via dedicated zones
- Servers VLAN accepts inbound from trusted VLANs only
Zone Layout¶
Assign VLANs to zones as follows:
| Zone | Type | Networks | Purpose |
|---|---|---|---|
| Internal | built-in | GenPop (VLAN 1), Personal (VLAN 2), Servers (VLAN 4), Management (VLAN 10) | Trusted traffic — household and infra |
| IoT | custom | IoT (VLAN 5), Appliances (VLAN 3) | Untrusted devices — internet only |
| Security | custom | Security (VLAN 6) | Cameras — isolated except NVR |
| External | built-in | WAN | Internet |
| Gateway | built-in | UDM management | DNS, DHCP, admin UI |
| VPN | built-in | Teleport, WireGuard, Unifi VPN | Remote access |
Custom zones (IoT, Security) deny all traffic to other zones by default. You only need to add explicit ALLOW policies where needed.
Do NOT put IoT in the Hotspot zone — the captive portal will trigger and break device connectivity.
Policies¶
Important: Policies are evaluated top-to-bottom. Create ALLOW policies before the BLOCK policy to avoid locking yourself out. The block-all should be the last policy created in each zone pair.
Step 1: Allow trusted VLANs to reach Servers¶
Policy: Allow GenPop → Servers (Internal → Internal)¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Network: GenPop (VLAN 1) |
| Destination zone | Internal |
| Destination | Network: Servers (VLAN 4) |
| Action | Allow |
Policy: Allow Personal → Servers (Internal → Internal)¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Network: Personal (VLAN 2) |
| Destination zone | Internal |
| Destination | Network: Servers (VLAN 4) |
| Action | Allow |
Policy: Allow Personal → Appliances (Internal → IoT)¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Network: Personal (VLAN 2) |
| Destination zone | IoT |
| Destination | Network: Appliances (VLAN 3) |
| Action | Allow |
Policy: Allow Personal → IoT (Internal → IoT) — required for HomePod AirPlay¶
HomePod and Apple TV are on IoT VLAN 5, not Appliances VLAN 3. A separate allow is required; mDNS alone is not enough for streaming.
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Network: Personal (VLAN 2) |
| Destination zone | IoT |
| Destination | Network: IoT (VLAN 5) |
| Action | Allow (not Block — the name field does not set action) |
| Allow return traffic | On (create_allow_respond: true in API) — required for AirPlay streaming |
Action Allow alone is not enough: without Allow return traffic, the phone
can reach the HomePod (outbound SYN is logged) but TCP sessions never complete.
Verified on kitchen HomePod (192.168.7.124) from KrustyKrab, 2026-06-26.
Fix script: scripts/fix-personal-iot-airplay.py
(requires UNIFI_* in GitHub secrets or local .env). Manual steps:
phase-7r AirPlay troubleshooting.
Policy: Allow Management → All (Internal → Internal) — create this first¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Network: Management (VLAN 10) |
| Destination zone | Internal |
| Destination | Any |
| Action | Allow |
Step 2: Block inter-VLAN traffic in Internal zone¶
With the allow exceptions in place, now add the block policies.
Policy: Drop invalid state (Internal → Internal)¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Any |
| Destination zone | Internal |
| Destination | Any |
| State | Invalid only |
| Action | Drop |
Policy: Block inter-VLAN (Internal → Internal) — create this last¶
| Field | Value |
|---|---|
| Source zone | Internal |
| Source | Any |
| Destination zone | Internal |
| Destination | Any |
| State | New, Invalid only (do NOT block Established/Related) |
| Action | Block |
The UDM will warn "Block All Traffic?" — this is expected. Your allow policies from Step 1 will match first, so allowed flows are unaffected.
Step 3: Security zone — no policies to Internal needed¶
Cameras write to Unifi Protect on the UDM SE (Gateway zone) via ports 7442, 7444, and 7550 — not to whrrr or any host on the Servers VLAN. The Security zone's default-deny to Internal is correct as-is.
Step 4: IoT zone — no policies needed¶
The IoT zone is custom, so it blocks all traffic to Internal, Security, and Gateway by default. IoT devices can still reach External (internet). No additional policies required — the default-deny does the work.
Step 5: Block IoT/Security access to Gateway¶
Prevent untrusted devices from reaching the UDM admin interface.
Policy: Block IoT → Gateway (IoT → Gateway)¶
| Field | Value |
|---|---|
| Source zone | IoT |
| Source | Any |
| Destination zone | Gateway |
| Destination | Any |
| Port | 80, 443, 22 |
| Action | Block |
Policy: Block Security → Gateway (Security → Gateway)¶
| Field | Value |
|---|---|
| Source zone | Security |
| Source | Any |
| Destination zone | Gateway |
| Destination | Any |
| Port | 80, 443, 22 |
| Action | Block |
Note: You may want to allow DNS (port 53) from IoT → Gateway if devices
need to resolve DNS via the UDM. If DNS is served by AdGuard on infra-services
(192.168.6.17), this isn't needed — but IoT devices won't be able to reach
it either since IoT → Internal is blocked. In that case, allow IoT → Internal
with destination IP 192.168.6.17 port 53 only.
Policy Summary (Zone Matrix)¶
→ Internal → IoT → Security → External → Gateway
Internal BLOCK* — — ALLOW ALLOW
IoT BLOCK BLOCK — ALLOW BLOCK†
Security BLOCK — BLOCK ALLOW BLOCK†
VPN ALLOW — — ALLOW ALLOW
* Block inter-VLAN new connections, with exceptions for GenPop/Personal/Mgmt → Servers
† Block management ports (80, 443, 22) only; Protect camera traffic (7442/7444/7550) unaffected
UDM Configuration Steps¶
Prerequisites¶
- Ensure you're on UniFi Network 9.x+ with gateway firmware 4.1+
- Back up your UDM config before enabling ZBF (Settings > System > Backup)
Enable Zone-Based Firewall¶
- Go to Settings > Security
- Click Upgrade on the ZBF notification banner
- Existing rules will auto-migrate — you'll clean those up after
Create Custom Zones¶
- Go to Settings > Security > Zones
- Create zone IoT — assign networks: IoT (VLAN 5), Appliances (VLAN 3)
- Create zone Security — assign network: Security (VLAN 6)
Create Policies¶
Add policies in the order listed above. Use the Zone Matrix to navigate: click on a zone pair (e.g., Internal → Internal) then "Create Policy".
Clean Up Migration Policies¶
After enabling ZBF, delete auto-migrated policies (IDs 30000+) that are redundant where possible. Some auto-migrated "(Return)" rules (IDs 30000+) overlap with Allow return traffic on custom allows — either approach can work. For cross-zone allows you create (e.g. Personal → IoT), enable Allow return traffic on the policy itself; do not assume return is implicit across custom zones (AirPlay broke with Allow only until return was enabled, 2026-06-26).
Most auto-migrated policies have a lock icon in the UDM UI and cannot be deleted. These are system-managed rules that UniFi considers part of the base ZBF configuration. They are functionally harmless — custom ZBF policies (IDs 10000+) take precedence. No action needed.
Verify¶
- A device on GenPop can reach
192.168.6.17(infra-services) — pass - A device on IoT CANNOT reach
192.168.6.17— pass - A camera on Security CANNOT reach
192.168.6.17— pass - Cameras still recording in Unifi Protect (UDM ports 7442/7444/7550) — pass
- Management device can reach everything — pass
Rollback¶
Disable individual policies in the Zone Matrix. To revert fully, delete the custom zones (IoT, Security) — their networks return to Internal with default allow-all behavior.
Note: Enabling ZBF is one-way. You cannot revert to the old firewall rule system. However, you can effectively achieve allow-all by removing the inter-VLAN block policy in Internal → Internal.