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
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.