Trusted, IoT, and Guest VLANs on a MikroTik RB5009
Split a flat LAN with two UniFi APs on hybrid trunks and a reviewable east-west firewall. Pure IPv4 plus 802.1Q — the first layer of the CGNAT build log.
Overview
This is the first-layer companion to the
CGNAT build log: 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 CGNAT build's 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.
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.
One always-on device is enough. The UniFi controller can also run on this
same RB5009 as RouterOS containers — no second box — which is its own
companion post:
Running the UniFi controller on the router itself.
Address plan
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 of the
CGNAT build log is added later, 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.
2. Conventions and placeholders
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.
3. Bridge VLAN table and L3 gateways
Turn on bridge VLAN filtering, declare the two VLAN interfaces, mark the AP
uplinks as trunks, and give each VLAN a gateway address.
VLAN bridge + gateways
bash
1/interface/bridge set[find name=bridge] vlan-filtering=yes
23/interface/vlan addinterface=bridge name=vlan-iot vlan-id=104/interface/vlan addinterface=bridge name=vlan-guest vlan-id=2056# Bridge VLAN table — hybrid trunks on ether2/ether3 (to APs),7# access-only LAN on ether4/ether5.8/interface/bridge/vlan
9addbridge=bridge vlan-ids=1untagged=bridge,ether2,ether3,ether4,ether5 comment="main LAN untagged"10addbridge=bridge vlan-ids=10tagged=bridge,ether2,ether3 comment="IoT to UniFi APs"11addbridge=bridge vlan-ids=20tagged=bridge,ether2,ether3 comment="Guest to UniFi APs"1213/ip/address addaddress=192.168.89.1/24 interface=vlan-iot
14/ip/address addaddress=192.168.90.1/24 interface=vlan-guest
Main LAN stays VLAN 1 untagged so AP adoption and existing wired devices
stay boring; only IoT and Guest are tagged, sharing the same uplinks to the
APs. That makes the trunk ports genuinely hybrid (untagged + tagged on one
wire) — the cost being that they must be documented as such, which the bridge
VLAN table above does. APs boot and adopt on the main LAN with zero AP-side
VLAN config. Each VLAN also terminates on its own gateway, so inter-VLAN
traffic is routed and filterable rather than bridged — that is what makes the
Guest-can't-reach-IoT rule a one-liner.
4. DHCP scopes
Each VLAN gets its own pool, server, and network record, with the router
itself acting as DNS.
The router is the resolver for now. Replacing the upstream side of that with
encrypted DoH — and moving clients onto an RDNSS resolver address that
survives a renumber — is its own companion post:
Encrypted DNS with a stable resolver address on RouterOS.
That layer is optional and sits on top of this one.
5. Firewall — input services and east-west isolation
The 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 + forward firewall
bash
1# Input — place BEFORE defconf's "drop all not coming from LAN".2/ip/firewall/filter
3addchain=input action=accept in-interface=vlan-iot protocol=udp dst-port=67-68 comment="IOT: DHCPv4"4addchain=input action=accept in-interface=vlan-iot protocol=udp dst-port=53comment="IOT: DNS UDP"5addchain=input action=accept in-interface=vlan-iot protocol=tcp dst-port=53comment="IOT: DNS TCP"6addchain=input action=accept in-interface=vlan-guest protocol=udp dst-port=67-68 comment="GUEST: DHCPv4"7addchain=input action=accept in-interface=vlan-guest protocol=udp dst-port=53comment="GUEST: DNS UDP"8addchain=input action=accept in-interface=vlan-guest protocol=tcp dst-port=53comment="GUEST: DNS TCP"910# Forward — place BEFORE fasttrack / established accepts.11/ip/firewall/filter
12addchain=forward action=drop in-interface=vlan-iot out-interface=bridge connection-state=new comment="IOT !-> LAN"13addchain=forward action=drop in-interface=vlan-guest out-interface=bridge connection-state=new comment="GUEST !-> LAN"14addchain=forward action=drop in-interface=vlan-guest out-interface=vlan-iot connection-state=new comment="GUEST !-> IOT"
Isolation is enforced in forward, not by starving clients of DHCP/DNS in
input. Mixing the two layers makes the rules unreviewable later.
6. Verification
Associate a client to each SSID (and a wired client on the main LAN) and
confirm the boundary actually holds.
Segmentation smoke tests
bash
1# On a client in each VLAN:2ip-4 addr show # expect the right /24 per SSID3ping192.168.89.1 # own gateway: must succeed4ping192.168.88.1 # MUST fail from IoT and Guest5nslookup cloudflare.com 192.168.89.1 # router resolves for IoT
A 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.
7. The IoT printer exception
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.
The exception is one host, one direction (LAN initiates; the established
reply is what carries the print job back). It is placed before the
IOT !-> LAN drop from §5 so only this destination punches through —
everything else on the IoT VLAN stays isolated.