Build log · MikroTik RB5009 · VLAN foundation
Trusted, IoT, and Guest VLANs on RouterOS
Trusted, IoT, and Guest VLANs on a single RB5009, with UniFi AP hybrid trunks and a reviewable east-west firewall.
Build log · MikroTik RB5009 · VLAN foundation
Trusted, IoT, and Guest VLANs on a single RB5009, with UniFi AP hybrid trunks and a reviewable east-west firewall.
This is the foundation companion in the RB5009 CGNAT series: split a flat home LAN into a trusted main network, an IoT VLAN, and a Guest VLAN on a single MikroTik RB5009, with two UniFi 6 APs hanging off hybrid-trunk ports.
It depends on nothing further up the stack. There is no IPv6, no VPS, and no WireGuard here — this is plain IPv4 plus 802.1Q VLANs and a reviewable firewall. The series' IPv6, DoH, and failover layers all sit on top of exactly this segmentation; none of them are needed to stand it up, and this post is the part you can apply on day one regardless of what your WAN looks like.
Every numbered section is paste-ready against a defconf RouterOS v7 box. The italic notes are the rationale — the trade-off being made and why.
Internet / WAN
│
┌─────────────┴─────────────┐
│ MikroTik RB5009 — edge │
│ • per-VLAN L3 gateway │
│ • DHCPv4 per VLAN │
│ • east-west firewall │
└──┬──────────┬──────────┬──┘
│ │ │
ether2/3 (hybrid trunks) ether4/5 (LAN-only access)
│
┌──────────┴──────────┐
│ UniFi 6 APs ×2 │
│ untagged = mgmt │
│ tag 10 = IoT SSID │
│ tag 20 = Guest SSID│
└──────────┬──────────┘
│
LAN 1 IoT 10 Guest 20
192.168.88/24 .89/24 .90/24
One box does the LAN-side work: it terminates the WAN (whatever it is), gives each VLAN an L3 gateway, runs DHCPv4 per VLAN, and enforces isolation. Two UniFi 6 APs hang off bridge ports configured as hybrid trunks — untagged frames carry AP management on the main LAN, tagged frames carry IoT and Guest SSID traffic.
| VLAN | Tagging | Role | IPv4 |
|---|---|---|---|
| VLAN 1 | untagged | main LAN, AP mgmt | 192.168.88.0/24 |
| VLAN 10 | tagged | IoT SSID | 192.168.89.0/24 |
| VLAN 20 | tagged | Guest SSID | 192.168.90.0/24 |
Each VLAN is a separate L3 boundary at the router. When the IPv6 layer from
the series is added later — either the
VPS path or the
Route64 path — each VLAN simply gains a
matching :1::/64 / :10::/64 / :20::/64 prefix on the same interfaces
created here. The IoT and Guest VLAN IDs deliberately reuse their numbers
as the IPv6 slice IDs so the mapping stays obvious in tcpdump.
The snippets assume the defconf bridge name bridge and ports
ether2–ether5; rename the interfaces where they appear to match your box.
The rest of the text is literal RouterOS — there are no placeholders to
substitute in this layer.
Turn on bridge VLAN filtering, declare the two VLAN interfaces, mark the AP uplinks as trunks, and give each VLAN a gateway address.
/interface/bridge set [find name=bridge] vlan-filtering=yes
/interface/vlan add interface=bridge name=vlan-iot vlan-id=10
/interface/vlan add interface=bridge name=vlan-guest vlan-id=20
# Bridge VLAN table — hybrid trunks on ether2/ether3 (to APs),
# access-only LAN on ether4/ether5.
/interface/bridge/vlan
add bridge=bridge vlan-ids=1 untagged=bridge,ether2,ether3,ether4,ether5 comment="main LAN untagged"
add bridge=bridge vlan-ids=10 tagged=bridge,ether2,ether3 comment="IoT to UniFi APs"
add bridge=bridge vlan-ids=20 tagged=bridge,ether2,ether3 comment="Guest to UniFi APs"
/ip/address add address=192.168.89.1/24 interface=vlan-iot
/ip/address add address=192.168.90.1/24 interface=vlan-guestEach VLAN gets its own pool, server, and network record, with the router itself acting as DNS.
/ip/pool add name=iot-pool ranges=192.168.89.100-192.168.89.200
/ip/pool add name=guest-pool ranges=192.168.90.100-192.168.90.200
/ip/dhcp-server add name=iot-dhcp interface=vlan-iot address-pool=iot-pool lease-time=1d
/ip/dhcp-server add name=guest-dhcp interface=vlan-guest address-pool=guest-pool lease-time=1d
/ip/dhcp-server/network add address=192.168.89.0/24 gateway=192.168.89.1 dns-server=192.168.89.1
/ip/dhcp-server/network add address=192.168.90.0/24 gateway=192.168.90.1 dns-server=192.168.90.1The input chain accepts only the router services the VLANs actually need; the forward chain drops new flows back into trusted networks. Established replies are not affected, which is what makes narrow per-host exceptions practical later.
# Input — place BEFORE defconf's "drop all not coming from LAN".
/ip/firewall/filter
add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=67-68 comment="IOT: DHCPv4"
add chain=input action=accept in-interface=vlan-iot protocol=udp dst-port=53 comment="IOT: DNS UDP"
add chain=input action=accept in-interface=vlan-iot protocol=tcp dst-port=53 comment="IOT: DNS TCP"
add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=67-68 comment="GUEST: DHCPv4"
add chain=input action=accept in-interface=vlan-guest protocol=udp dst-port=53 comment="GUEST: DNS UDP"
add chain=input action=accept in-interface=vlan-guest protocol=tcp dst-port=53 comment="GUEST: DNS TCP"
# Forward — place BEFORE fasttrack / established accepts.
/ip/firewall/filter
add chain=forward action=drop in-interface=vlan-iot out-interface=bridge connection-state=new comment="IOT !-> LAN"
add chain=forward action=drop in-interface=vlan-guest out-interface=bridge connection-state=new comment="GUEST !-> LAN"
add chain=forward action=drop in-interface=vlan-guest out-interface=vlan-iot connection-state=new comment="GUEST !-> IOT"Associate a client to each SSID (and a wired client on the main LAN) and confirm the boundary actually holds.
# On a client in each VLAN:
ip -4 addr show # expect the right /24 per SSID
ping 192.168.89.1 # own gateway: must succeed
ping 192.168.88.1 # MUST fail from IoT and Guest
nslookup cloudflare.com 192.168.89.1 # router resolves for IoTA client on the IoT or Guest SSID that gets a lease in its own subnet, reaches its own gateway and the internet, but cannot ping the main-LAN gateway, is the whole proof: the VLANs are isolated boundaries, not just separate address ranges.
The default policy from §5 is isolation; this is the canonical narrow exception, kept deliberately small. Pin the printer's IP in the IoT scope, allow trusted LAN clients to initiate to that one address, and reflect mDNS between LAN and IoT so AirPrint discovery works. Guest stays excluded on purpose.
/ip/dhcp-server/lease add server=iot-dhcp mac-address=AA:BB:CC:DD:EE:FF \
address=192.168.89.200 comment="Brother printer"
/ip/firewall/filter add chain=forward action=accept connection-state=new \
in-interface=bridge out-interface=vlan-iot dst-address=192.168.89.200 \
place-before=[find where comment="IOT !-> LAN"] \
comment="LAN -> printer"
/ip/dns set mdns-repeat-ifaces=bridge,vlan-iot
/ip/firewall/filter add chain=input action=accept in-interface=vlan-iot \
protocol=udp dst-address=224.0.0.251 dst-port=5353 \
comment="IOT: mDNS to router"Comments