Setting up a DMZ VLAN with UniFi for public-facing services

18 June 2025 ยท Luke Gillmore-White

When you self-host something public-facing, you’re opening a door from the internet into your home. The question isn’t whether to open it โ€” it’s how far that door goes. By default, if your server gets compromised, an attacker has a foothold inside your LAN and can potentially reach everything else on your network: NAS shares, other VMs, management interfaces. A DMZ VLAN removes that risk entirely by making lateral movement structurally impossible rather than just unlikely.

What is a DMZ VLAN?

A DMZ (Demilitarised Zone) is an isolated network segment that sits between the internet and your internal LAN. Traffic from the internet can reach servers in the DMZ, but the DMZ cannot initiate connections back into your main network. Even if a server in the DMZ is fully compromised โ€” root shell, the works โ€” the attacker is contained to that segment. They can see the internet, they can see other things in the DMZ, and that’s it.

Network design

My setup uses three VLANs managed by a UniFi Dream Machine:

VLAN 10 โ€” Management   10.0.0.0/24     Proxmox host, UniFi AP, switch management
VLAN 20 โ€” LAN          192.168.50.0/24 Main trusted devices, workstations, NAS
VLAN 30 โ€” DMZ          172.16.30.0/24  Public-facing servers only

Each VLAN gets its own network object in the UniFi controller (Settings โ†’ Networks โ†’ Create New Network), with Inter-VLAN Routing on by default โ€” which is exactly what the firewall rules below exist to restrict, since UniFi routes between VLANs unless you explicitly tell it not to.

The Proxmox host has two NICs โ€” one on the management VLAN for host administration, and one dedicated to the DMZ bridge for LXC containers. This physical separation matters: even a misconfigured firewall rule can’t accidentally bridge the two networks if they’re not sharing a NIC in the first place.

Proxmox network configuration

vmbr0 โ€” Management bridge
  IP:      10.0.0.10/24
  Gateway: 10.0.0.1
  Bridge ports: eno1 (untagged, VLAN 10 access port on the switch)

vmbr1 โ€” DMZ bridge
  IP:      none (no host IP on this bridge at all)
  Gateway: none
  Bridge ports: eno2 (untagged, VLAN 30 access port on the switch)

The DMZ bridge deliberately has no IP address on the Proxmox host itself. Containers attached to vmbr1 get their own IPs within 172.16.30.0/24, but the hypervisor host has no presence on that network โ€” so even if a container broke out to the host’s networking stack, there’s no DMZ-side address to pivot from.

UniFi firewall rules

Configured under Settings โ†’ Firewall & Security โ†’ Firewall Rules, in three groups by traffic direction:

WAN IN rules (internet โ†’ DMZ):

Rule 1: Allow established/related sessions
Rule 2: Allow WAN โ†’ DMZ server port 443 (HTTPS)
Rule 3: Allow WAN โ†’ DMZ server port 80  (HTTP โ†’ redirect only)
Rule 4: Drop all other WAN โ†’ DMZ traffic

LAN IN rules (LAN โ†’ DMZ):

Rule 5: Allow LAN โ†’ DMZ (for management access โ€” SSH, monitoring โ€” from trusted devices only)

DMZ IN rules (DMZ โ†’ everything else) โ€” this is the one that actually matters:

Rule 6: Allow DMZ โ†’ WAN (outbound only โ€” package updates, Let's Encrypt renewal, RSS feed fetches)
Rule 7: Drop DMZ โ†’ LAN
Rule 8: Drop DMZ โ†’ Management VLAN

Rule 7 and Rule 8 are the ones doing the real work. Everything else is just “let legitimate traffic through” โ€” these two are “even if something goes catastrophically wrong in the DMZ, it goes nowhere else.” UniFi processes firewall rules top-to-bottom per direction group, and since Inter-VLAN Routing is on by default, without these explicit drop rules the DMZ would happily route to the LAN and management VLANs with zero friction.

A mistake worth flagging

Early on I had Rule 7 and Rule 8 below a broader “Allow DMZ โ†’ Any” rule I’d added for troubleshooting and forgot to remove. Broad allow rules placed before specific deny rules silently defeat the deny rules entirely โ€” UniFi (like most stateful firewalls) stops evaluating on first match. Worth periodically reviewing the full rule order, not just confirming the deny rules exist somewhere in the list.

Verifying isolation actually works

Rather than trusting the rule set, I tested it directly from inside the DMZ container:

# From the DMZ LXC โ€” should succeed (outbound to internet)
curl -I https://1.1.1.1

# From the DMZ LXC โ€” should time out / fail (blocked by Rule 7)
ping 192.168.50.1
nc -zv -w3 192.168.50.1 22

Both blocked attempts correctly timed out rather than getting an active refusal, confirming the traffic was being silently dropped at the firewall rather than reaching the LAN and being rejected there โ€” an important distinction, since a “connection refused” would mean the packet actually arrived.

Result

The blog is publicly accessible on ports 80 and 443. All other inbound traffic is dropped at the UniFi firewall before it reaches the LXC. The LXC cannot reach the rest of the home network under any circumstances, verified directly rather than assumed, so a compromise of the web server stays contained entirely within the DMZ.