Homelab Rebuild Part 7: One SSID, and the Network Decides

What this is about Link to heading

Part 1 established that one SSID per VLAN doesn’t work โ€” every SSID costs airtime, and with five or six of them you lose measurable throughput.

The alternative is the one companies use: a single SSID, and the network decides per device. The device authenticates with its own credentials, a RADIUS server checks them and sends the VLAN assignment back with the answer.

This is what the whole rebuild has been leading up to. And it’s the part that cost me by far the most time.

First: fix the VLAN structure Link to heading

Before anything can be assigned, it has to be clear where to.

My numbering follows trust level, not device type. The benefit: you can tell from an address where it belongs.

VLAN Network Contents
10 10.0.1.0/24 Proxmox, NAS, backup server
20 10.0.20.0/24 Workstation, laptop
30 10.0.30.0/24 Telephony
41 10.0.41.0/24 Phones
42 10.0.42.0/24 HTPC
43 10.0.43.0/24 Light controller
44 10.0.44.0/24 Printer, receiver, console

Phones sit deliberately in the 40 range. They’re worth more than the printer and less than the work machine โ€” a phone is constantly on someone else’s network, and it runs apps whose behaviour I don’t control.

The trick when restructuring Link to heading

Originally everything lived in one VLAN. The obvious move would have been to migrate the servers into a new segment. That would have meant chasing down every reference: ACME addresses, backup targets, DNS entries, reservations.

So the other way round: move the clients out and leave the servers put. The old catch-all VLAN becomes the server segment by attrition. Not a single reference had to change.

The real gain is something else, and it mattered more to me than tidy numbering: the native VLAN ends up empty. Plug an unknown device in somewhere and it no longer lands next to my servers.

How the assignment works Link to heading

Diagram: a device authenticates to the access point, which asks FreeRADIUS, gets a VLAN ID back and places the device in the matching VLAN.

The device doesn’t know its VLAN โ€” it gets assigned one.

Each device gets its own credentials, not a shared key. On the RADIUS server there’s one entry per device with three attributes that together make up the VLAN assignment:

htpc-coreelec Cleartext-Password := "..."
    Tunnel-Type = "VLAN",
    Tunnel-Medium-Type = "IEEE-802",
    Tunnel-Private-Group-Id = "42"

The practical benefit: when the policy changes, I change one entry on the server. Not twelve devices. And when a device has to go, I delete its entry and nothing else is affected.

What went wrong Link to heading

Four problems, and none of them appear in the guides I’d read beforehand.

The file that never gets read Link to heading

This one held me up longest, and in hindsight it’s annoyingly simple.

I’d been maintaining my device entries in /etc/freeradius/3.0/users. That’s what practically every guide online says. And the server did find them โ€” the debug log clearly showed Matched entry htpc-coreelec at line 47.

It just stubbornly returned the wrong VLAN number. I restarted the service ten times, checked the file repeatedly, went looking for duplicate entries.

With FreeRADIUS 3, users isn’t read at all. The file is a leftover from version 2 that distribution packages still ship for compatibility. What actually gets read is /etc/freeradius/3.0/mods-config/files/authorize.

You can verify it in one line:

grep filename /etc/freeradius/3.0/mods-enabled/files

It says filename = ${moddir}/authorize โ€” and moddir points at mods-config/files/.

Because both files happened to be the same size and the server appeared to find the entries, everything looked correct. I’ve since renamed the dead file so I can’t reach for it again.

Attributes stuck inside the tunnel Link to heading

The second one was subtler. In the debug log, the inner PEAP tunnel returned the VLAN attributes correctly โ€” but they were missing from the final Access-Accept sent to the access point.

PEAP has two layers: an outer, anonymous one and an inner, encrypted one where the actual authentication happens. Attributes from the inner tunnel are not automatically passed outward. The access point only sees the outer reply โ€” and that one was empty.

The switch for this lives in the EAP module config, inside the peap block:

use_tunneled_reply = yes

After that the three attributes appeared in the final Access-Accept, and the access point knew for the first time where to put the device.

Two models that don’t fit together Link to heading

Now the server returned the right thing, the access point understood it โ€” and devices still landed in the wrong network.

The reason is an architectural conflict you only see once you know both sides.

hostapd builds a separate bridge per VLAN. That’s the classic model from a time when bridges didn’t understand VLANs. For each assigned VLAN it creates an interface, builds a bridge and attaches a tagged uplink.

Current OpenWrt does it the other way round: a single bridge with VLAN filtering and a table of which port carries which VLANs.

Both models describe the same goal and are still incompatible. In the log it looked like this:

VLAN: vlan_add: ADD_VLAN_CMD failed for br-lan: File exists
VLAN: br_addif: ... failed for br_name=br-lan.41: Symbolic link loop

hostapd wanted to create an interface the bridge already carried, then insert itself into itself.

The bridge between the two worlds is a small hotplug script. It fires when hostapd creates a VLAN interface, moves it into the right bridge and sets the matching VLAN membership:

[ "$ACTION" = "add" ] || exit 0
case "$DEVICENAME" in
  *-ap*.*)
    VID="${DEVICENAME##*.}"
    [ "$VID" -gt 1 ] 2>/dev/null || exit 0
    ip link set "$DEVICENAME" master br-lan
    bridge vlan add dev "$DEVICENAME" vid "$VID" pvid untagged
    bridge vlan del dev "$DEVICENAME" vid 1
    ip link set "$DEVICENAME" up
    ;;
esac

The nice part: it’s generic. Every future VLAN works without further effort.

The fault that looks like a client problem Link to heading

And then one more, which cost me a dismantled media player.

After several rounds of configuration my HTPC wouldn’t join any Wi-Fi at all โ€” not even the old one. No password prompt, just an error. I pulled the device out of its enclosure, put its storage card in a computer and deleted config files.

The fault was at the access point:

STA ... IEEE 802.11: authenticated
STA ... IEEE 802.11: Could not set STA to kernel driver

The Wi-Fi driver had got itself wedged after several restarts of the radio service. Rebooting the access point fixed it โ€” a wifi down; wifi up wasn’t enough, because the driver itself was affected.

The lesson: when a client stops connecting, check the access point’s log first, not the device. Had I done that earlier, I’d have saved myself the disassembly.

What devices can and can’t do Link to heading

In the end, devices fall into three groups.

It just works. Phone and laptop. Enter credentials, connect, done. On the phone you should also set an anonymous identity, otherwise the username travels in clear text in the outer part of the exchange.

It works with effort. My media player runs CoreELEC and manages its network through connman. That offers no UI for 802.1X โ€” but it does read config files:

[service_olient]
Type = wifi
Name = my-ssid
EAP = peap
Phase2 = MSCHAPV2
Identity = htpc-coreelec
AnonymousIdentity = anonymous
Passphrase = ...

This works, but it’s unpleasant to handle. Every change drops the connection you’re working over. If you do this over SSH, set a timer first that reverts the config after a few minutes and puts the device back on the old network โ€” the same principle as the failsafe pattern from part 3.

It doesn’t work at all. My light controller runs WLED. The release notes for the current version list 802.1X support as a new feature. The interface has no fields for it.

The explanation was in the corresponding pull request: the feature was implemented and merged โ€” and then deliberately put behind a compile switch, because it costs over 100 KB of flash. It’s enabled in no published binary.

That isn’t a fault in the software, it’s a defensible trade-off. But it says so in no release notes, and I spent two hours looking before I read the pull request.

The lesson for all three groups: before you rebuild, check which devices can do 802.1X at all. And for those that can’t, plan a second SSID with a classic key that terminates in an untrusted VLAN.

Where things stand Link to heading

One SSID, several devices, each with its own credentials, each in the right network. Phone and media player are fully separated even though they share the same access point and the same SSID.

What’s still open: firewall rules in the new VLANs are still set to allow everything. Tightening them is the next step โ€” and only then does the separation become a security boundary.

Next up Link to heading

Before the rules, one more building block that makes life nicer: an internal certificate authority. So internal services can do HTTPS without the browser complaining every time.


Part of a series about rebuilding my home network. As of August 2026. Questions, corrections and your own experiences are welcome in the comments.