---
title: "Walking around for fast roaming's sake"
description: "It took four walks through the house with logs open, two wrong turns, and one evening of config changes before my iPhone started fast roaming between our OpenWrt access points."
url: "https://michael.zazon.us/notes/walking-around-for-fast-roamings-sake"
published: "2026-04-15T12:00:00-04:00"
kind: "build-log"
subject: "homelab"
tags: ["iphone","openwrt","wifi"]
series: "Home networking"
part: 2
---
I spent most of April 9th walking back and forth along the same loop in my house, watching the logs on the APs, trying to get 802.11r fast roaming working between my OpenWrt APs. I didn't see the iPhone do a fast transition until the evening, and the settings that eventually worked weren't the ones I'd changed in the morning.

Fast roaming mattered because the netgear orbi system and later the Eero mesh I replaced in [OpenWrt to the rescue](https://michael.zazon.us/notes/openwrt-to-the-rescue) had it, and losing it showed up on calls. Our previous systems had fast roaming, and our Apple devices handled it beautifully as we moved around the house. Cell signal at home is spotty on both of our carriers, so our calls ride on Wi-Fi calling and FaceTime audio, and Wi-Fi has to be consistent and reliable. As we move between APs, the handoff should be seamless, with no dropouts. I also work from my home office from time to time, and strong Wi-Fi around the house is critical for that.

[Diagram: A normal roam drops the old AP, authenticates and runs the full key handshake, while an 802.11r fast transition reuses keys and reassociates in two short exchanges](https://michael.zazon.us/notes/walking-around-for-fast-roamings-sake)

## The walks

By keeping an eye on the AP association, signal-to-noise ratio (SNR), and packet loss while walking around the house, I feel confident in my choices for AP placement and power levels. I always walked the same route during my walks: starting in the kitchen, then going through the living room, master bedroom, a kid's room, up to the office (third floor), and then back down.

Walks 1 and 2 were from that morning. In both, the iPhone does 14 full handshakes with 0 fast transitions (FT). That means every time it moves to a new AP, it needs to fully authenticate again instead of doing the shortened 802.11r exchange. At the end of the loop, you can see it ping-ponging between the kitchen and living room APs, doing 5 reassociations in 45 seconds.

My first wrong turn was a missing line. I searched the OpenWrt-generated hostapd config for `ieee80211r=1`, didn't find it, and assumed 802.11r was off. That was a dead end. OpenWrt signals FT through FT entries in `wpa_key_mgmt` (like FT-PSK), not a separate line. It's in the [OpenWrt wireless documentation](https://openwrt.org/docs/guide-user/network/wifi/basic) if you know where to look.

The second wrong turn took me longer to get out of. Each access point had been deriving its own NAS identifier, but my agent told me that all access points in a mobility domain needed to share one. So I set a single, shared `nas_identifier` on all three access points with Ansible, rolling them out in the following order: living room, kitchen, router. When I walked through the house for the third time, I was still at 0 fast transitions.

## Good enough, until the master bath

I stopped here because all the important spots in my house were already at -65 dBm or better (living room: -35, kitchen: -53, master bedroom: -64, office: -55). The roam glitch is about 300 ms, which I only notice when I'm on a call and walking around. So I decided that was good enough for now and wrote up this status.

That afternoon, I noticed my iPhone flapping in the master bathroom. The best AP was only -72 dBm, the other two were -80 and -91. I made two changes: I changed all APs to WPA2 only with FT-PSK, dropping the SAE/WPA3 transition mode, and I increased the 5 GHz transmit power on my router from 14 to 17 dBm.

In walk 4, the flapping was fixed and the router kept my phone connected across floors, at a 1200 Mbit/s connection. There were still 0 fast transitions. The APs now ran the following configuration:

```
wpa=2
wpa_key_mgmt=WPA-PSK FT-PSK
mobility_domain=<domain>
ft_psk_generate_local=1
ft_over_ds=0
reassociation_deadline=20000
```

So my working theory at this point was that iOS will deauthenticate from the old AP before roaming, and that means that FT is impossible no matter what the APs offer.

## The evening fix

I rejected that theory and went back to the sources myself, namely [Apple's page on Wi-Fi roaming support in Apple devices](https://support.apple.com/guide/deployment/dep98f116c0f/web) and the OpenWrt forum threads. The confident answer I'd been given was wrong (APs don't need to share a NAS identifier, the community best practice is to configure a unique NAS-ID per AP) and during my review, I found an even bigger problem with the change I had made that afternoon: SAE requires Protected Management Frames (PMF), so my WPA3 transition mode had turned on PMF without me realizing, and when I switched to pure WPA2, PMF got turned off. And some iOS versions refuse 802.11r without PMF!

That evening, I made 6 changes and ran Ansible once to apply them all. I set the canary order so the AP my iPhone was connected to went last. The 6 settings I wanted were:

| Setting | Why |
| --- | --- |
| `ieee80211w=1` | PMF in optional mode. Some iOS versions need it before they use FT, and clients without PMF can still connect. |
| `nas_identifier` unique per AP | Community best practice. Each access point has its own identity in the logs. |
| `proxy_arp=1` | The access point answers ARP for clients in power save, so the phone stays reachable on the LAN while it sleeps and when it roams. |
| `wnm_sleep_mode_no_keys=1` | A KRACK-family mitigation for WNM Sleep Mode key reinstallation (CVE-2017-13087). |
| `reassociation_deadline=20000` | About 20 seconds for the client to finish the FT reassociation. OpenWrt defaults to this already, but I set it explicitly so a future update can't change it on me. |
| 802.11k neighbor reports from DAWN | [DAWN](https://github.com/berlin-open-wireless-lab/DAWN) injects neighbor reports so clients know which access points are nearby before they roam. |

After the run, I could successfully initiate FTs from both my iPhone and MacBook, and I observed 802.11k beacon reports flowing. Most importantly, all 31 stations remained connected without any PMF-induced connection drops.

## Which change did it

I never isolated which of the six changes enabled FT because I shipped them all together, but my best guess is PMF.. it was previously supplied by SAE transition mode and got silently turned off when I switched to pure WPA2 in the afternoon. The per-AP NAS-ID is a best practice, but not the fix. With `ft_psk_generate_local=1`, hostapd derives the FT keys locally on every association from the identifier the client presents, so shared and unique NAS-IDs behave the same. That's what the Ansible role's comment says, after all. The other thing I learned is: have patience. iOS needs a warm-up before it starts using FT, so one walk with 0 fast transitions doesn't mean FT is broken.

I needed a way to ensure consistent settings across my entire AP fleet, both now and in the future, that were tuned for performance and resiliency. Because of this, I now have *all* of my settings defined in Ansible, which is managing every aspect of the configuration on the router and APs. We'll look at Ansible in the next part of this series.