OpenWrt multi-AP fleet policy

When several OpenWrt access points cover one building, treating them as independent devices produces inconsistent Wi-Fi: clients that stick to a weak AP, config drift after firmware updates, and channels that silently converge onto the same congested block. Running them as a fleet with one written-down policy — applied identically to every AP regardless of hardware — fixes this.

How it works

Every AP in the fleet runs in dumb AP mode: no routing, no DHCP server, just a bridge from Wi-Fi to the wired LAN, with a central router/firewall handling DHCP and routing. On top of that, four settings are enforced identically everywhere:

1. Seamless roaming (802.11r/k/v + a steering daemon)

Configure Fast BSS Transition (802.11r), Neighbor Reports (802.11k) and BSS Transition Management (802.11v) on the SSID(s) where roaming matters most (typically the highest-traffic/shortest-range band). See v and usteer for the full standards explanation, usteer internals, verification commands and a rollback procedure — summarized here as one of the fleet’s four policy pillars:

UCI optionValueMeaning
ieee80211r1Enable Fast BSS Transition
ft_psk_generate_local1Each AP generates FT keys independently (no need to share a key manually)
mobility_domain<4-hex-digit-id>Shared domain ID — must be identical on every AP
ft_over_ds0FT over the air (not over the distribution system)
ieee80211k1Enable Neighbor Reports
ieee80211v1Enable BSS Transition Management

The 802.11r/k/v parameters alone don’t perform any active steering — they only make roaming possible and fast once a client decides to move. A steering daemon (OpenWrt’s usteer is the common choice) is what actually nudges clients toward a better AP, by watching signal strength across the fleet and sending 802.11v requests. usteer must be installed and running on every AP — a missing or silently-stopped daemon leaves roaming looking “configured” (correct UCI options) while doing nothing.

Typical usteer thresholds:

OptionTypical valueMeaning
signal_diff_threshold~10 dBmSteer only if a neighboring AP is meaningfully stronger
min_snr~-80 dBmMinimum acceptable SNR before a client is considered poorly served
roam_scan_snr~-70 dBmSNR below which a neighbor scan is triggered
roam_kick_snr~-75 dBmSNR below which an 802.11v transition request is sent

Reasons to scope roaming to one band (e.g. 5GHz only) rather than applying it everywhere:

  • The shorter-range band has more frequent, more impactful roaming events.
  • IoT/smart-home devices on other SSIDs avoid any 802.11r client-compatibility edge cases.

2. Fixed 5GHz channels, auto 2.4GHz

Run the two bands with opposite channel policies:

  • 5GHz: fixed, never auto. Plan channels by hand based on which APs can actually hear each other (an interference graph, not just physical distance — building materials like metal- reinforced floors matter more than raw distance). Fixed channels stop each AP’s auto-channel- select (ACS) algorithm from independently re-converging onto the same “quiet-looking” channel over time, which is what causes multiple APs to silently pile onto one channel months after a careful initial rollout.
  • 2.4GHz: always auto, never fixed. 2.4GHz has far fewer non-overlapping channels and (in a residential/mixed environment) much more external interference from neighboring networks and non-Wi-Fi devices; a static assignment gets stale faster than ACS can adapt, so the usual logic is inverted here.

Gotcha: setting a DFS channel (the UNII-2/UNII-2e ranges, roughly 52-144 depending on regulatory domain) via UCI fails outright if the radio’s regulatory country code isn’t set first — hostapd rejects the channel immediately (NO-IR RADAR / “Could not select hw_mode and channel”), and the radio never starts, rather than just risking the wrong TX power. Always set country before or together with a DFS channel:

uci set wireless.radioN.country="<CC>"
uci set wireless.radioN.channel="<target>"
uci commit wireless
wifi reload radioN

A DFS channel change also triggers Channel Availability Check (CAC, commonly ~60 seconds) before the radio comes up — this is normal, not a failure.

3. IPv6 disabled fleet-wide on dumb APs

If the fleet’s upstream network doesn’t route IPv6, or IPv6 isn’t wanted on the Wi-Fi segment at all, disable it explicitly on every AP rather than leaving it to chance — an AP left at OpenWrt defaults will happily relay/advertise IPv6 it has no business handling.

# Disable IPv6 entirely on the LAN bridge device (kernel-level — no RA/DHCPv6/SLAAC possible)
uci set network.@device[<idx-of-lan-bridge>].ipv6='0'
 
# Remove any IPv6 prefix delegation on the lan interface (UCI section name, not a hostname)
uci -q delete network.lan.ip6assign  # wiki:allow
uci -q delete network.lan.ip6ifaceid  # wiki:allow
uci -q delete network.lan.ip6hint  # wiki:allow
 
# Belt-and-braces: explicitly disable IPv6 in dnsmasq/odhcpd too
uci set dhcp.lan.ra='disabled'  # wiki:allow
uci set dhcp.lan.dhcpv6='disabled'  # wiki:allow
uci set dhcp.lan.ndp='disabled'  # wiki:allow
 
uci commit network
uci commit dhcp
/etc/init.d/network restart

The device-level ipv6='0' on the LAN bridge is the important part — it prevents the kernel from doing anything IPv6 on that interface at all. The dhcp options are redundant given that, but kept for clarity of intent and defense in depth.

Verify with:

ip -6 addr show <lan-bridge>   # should show nothing
uci get dhcp.lan.ra            # should print 'disabled'  # wiki:allow

4. Consistent SSID/VLAN layout

Every AP should broadcast an identical set of “fleet” SSIDs, byte-identical in spelling (802.11r roaming and general client stickiness depend on the SSID string matching exactly — a stray space or case difference like 5Ghz vs 5GHz silently breaks roaming for that SSID, or creates what looks like two separate networks). A typical layout:

SSID patternScopeNotes
<Main> / <Main> 5GHzEvery APRoaming-enabled band gets the suffix
<Category> (e.g. home automation)Every APSeparate SSID per device category, own VLAN if segmented
<Guest>Every APSee VLAN note below
<Location> per-AP SSIDOne APOptional — lets a client pin itself to one physical AP

If a guest network needs real L2 isolation, model it as its own VLAN with its own subnet. Be aware this can have a real throughput cost on constrained/single-core hardware — see Pitfalls.

When to use it / trade-offs

  • Worth doing as soon as a “fleet” exists — even three or four APs benefit from a fixed channel plan and roaming, and the policy scales to any size without changing shape.
  • The fixed-5GHz/auto-2.4GHz split assumes 2.4GHz interference is the dominant problem and 5GHz interference is planable; in an environment with heavy 5GHz congestion too (e.g. dense apartment buildings), both bands may need manual planning instead.
  • 802.11r client compatibility is generally good today, but scoping it to one SSID/band rather than every network reduces the blast radius of any edge case.

Pitfalls

  • A missing steering daemon looks configured. 802.11r/k/v UCI options can be entirely correct while usteer (or equivalent) isn’t installed or has silently stopped — verify the process is running, not just the config.
  • SSID string drift breaks roaming silently. A single AP with a typo’d or differently-cased SSID doesn’t error — it just creates a client-visible inconsistency (or, worse, looks like a separate network entirely) that undermines the whole roaming setup. Audit exact SSID strings across the fleet periodically, not just once at rollout.
  • DFS channels fail hard without country set — not a soft warning, the radio doesn’t start. Set the regulatory domain on every radio before or with any DFS channel assignment.
  • ACS re-converges over time. A “temporarily fine” auto channel plan on 5GHz will often drift back to whatever channel looks quietest to each AP’s independent scan, undoing a manual balancing effort — this is exactly why 5GHz should be fixed, not just initially set.
  • A second managed network interface (e.g. a VLAN-backed guest bridge) can cost significant throughput on single-core/low-end hardware, independent of whether it’s actually passing traffic — the mere presence of an extra netifd-managed interface was enough to measurably hurt throughput on constrained devices in one real fleet. If guest isolation is wanted, budget time to benchmark it per hardware class rather than assuming it’s free.
  • v and usteer — deep dive on this policy’s roaming pillar.

Sources