Cloud gaming over WiFi on Linux is almost great. Then, every couple of minutes, it is not.
The stream hitches for a second or two, the client flashes a network warning, your inputs arrive late, and then everything recovers on its own. Not constant lag — periodic, short, maddening spikes. Plug in an Ethernet cable and it is perfect. The same laptop on Windows seems fine. So you blame the ISP, the router, the WiFi card, the driver, the phase of the moon. 🤷
It is none of those. Your WiFi card is deliberately looking away from your access point every 30 seconds, and NetworkManager is the one telling it to.
GeForce NOW asks for 15 Mbps at 720p60, 25 Mbps at 1080p60, 35 Mbps at 1600p120 — and under 80 ms of latency. Your WiFi has all the bandwidth it needs. What is missing is stability. A cloud gaming stream is real-time video plus a round trip for every input. There is no buffer to hide behind.
📡 One radio, one channel
A WiFi card has one radio, tuned to one channel. It cannot listen on channel 44 and talk on channel 1 at the same time. To find out what else is around, it has to hop away and listen — and while it is away, your traffic is not moving. The AP keeps retransmitting frames you never acknowledged, your own packets queue up in the driver, and latency spikes.
With a good driver a scan costs a few hundred milliseconds of extra latency. With a bad one it is seconds and packet loss. Either way it is exactly the shape that ruins a game stream: short, sharp, periodic. 🎮
🔍 Who asks for those scans?
Two programs working together:
- wpa_supplicant runs background scans (
bgscan) so it always has a fresh list of roaming candidates. That is how your laptop jumps to a better AP before the current one becomes unusable. - NetworkManager decides whether to enable that, and with which parameters.
NetworkManager’s defaults, straight out of nm-supplicant-config.c:
simple:30:-70:86400— the normal casesimple:30:-65:300— when it has seen more than one AP for your SSID (mesh, repeater, or a single router broadcasting 2.4 and 5 GHz under the same name) or when you are on WPA-Enterprise
The format is simple:<short interval>:<signal threshold>:<long interval>: below the threshold it scans every 30 seconds, above it goes quiet for the long interval (300 s, or a whole day).
Read that again. On a multi-AP network with a signal hovering around -65 dBm — an entirely normal number for a laptop on a sofa — wpa_supplicant scans every 30 seconds. And it does up to long / short + 1 of those (11 for the 300 s profile) before backing off. That is the “spikes every couple of minutes, then calm, then spikes again” pattern: a burst of scans triggered by the signal dipping under a threshold, and every scan is a channel hop.
NetworkManager itself also used to scan periodically (3 to 120 s, with backoff). In current versions it only does that while disconnected — once connected it hands the job to the supplicant. Same symptom, different author.
🔨 The fix: pin your profile to one access point
Every access point has a BSSID — the MAC address of that specific radio. Your SSID can be broadcast by several of them. Pick one and lock the connection profile to it.
# 1. find the BSSID of the AP you are on (the row with *)
nmcli -f IN-USE,BSSID,SSID,CHAN,SIGNAL,BARS device wifi list
# 2. lock the profile to that BSSID
nmcli connection modify "<YourSSID>" 802-11-wireless.bssid "AA:BB:CC:DD:EE:FF"
# 3. reconnect so it takes effect
nmcli connection up "<YourSSID>"
nmcli connection show lists your profile names if you are not sure what to quote. It has to be a full 12-hex-digit MAC — NetworkManager validates it and refuses anything else. To undo it, set an empty value:
nmcli connection modify "<YourSSID>" 802-11-wireless.bssid ""
🤔 Why that works
From NetworkManager’s source, nm-supplicant-config.c:
Don’t scan when the connection is locked to a specific AP, since intra-ESS roaming (which requires periodic scanning) isn’t being used due to the specific AP lock.
When the profile carries a BSSID, NetworkManager never writes a bgscan line into the supplicant configuration at all. No bgscan means no background scans, no channel hops, no spikes. The reference manual says the same thing in one sentence: “Locking a client profile to a certain BSSID will prevent roaming and also disable background scanning.”
You are not tweaking a threshold or fighting a heuristic. You are removing the reason for the scan to exist.
✅ Verify
nmcli -f 802-11-wireless.bssid connection show "<YourSSID>" # prints your pinned MAC
iw dev wlan0 link # BSSID stays the same, forever
ping -i 0.2 -D 1.1.1.1 # flat line, no bursts
Give the ping ten minutes. The old pattern shows up fast if it is still there.
⚠️ What you give up
- Roaming. Walk to the far end of the house and you stay stuck on the pinned AP with a weak signal. Pin the node nearest your desk or sofa, and unpin when you need to move around.
- Driver support. The docs call this “highly driver dependent”. Most mac80211-based cards honour it (Intel, Atheros/Qualcomm, and Realtek’s rtw89 on a recent kernel). Some vendor drivers quietly ignore it — if the BSSID still changes in
iw dev wlan0 link, yours did. - Fragility. If that AP reboots onto a different channel or gets a firmware update that changes its BSSID, the pin points at nothing and the profile will not connect until you clear it.
- Mesh. Same SSID on every node means you are choosing one node, on purpose. That is the point, but it is a choice.
🎛️ If it still scans
Some people keep seeing CTRL-EVENT-SCAN-STARTED after locking the BSSID. Two things worth checking before you give up:
- Explicit scans are still allowed. The lock disables background scanning. A scan somebody asks for still runs:
nmcli device wifi rescan, opening your network applet’s AP list, or a browser asking for geolocation through geoclue. Do not leave the WiFi menu open while playing. 📵 - The driver ignored the lock. Watch
iw dev wlan0 linkfor a while. If the BSSID changes, the pin is not being enforced.
If you genuinely need roaming — a laptop that moves around the house — the other knob is the scan threshold itself: the supplicant starts hunting for a better AP while your current signal is still perfectly usable. Raising the threshold to something like -80 dBm makes it stop scanning until the signal is actually bad. It is set per network block in wpa_supplicant, so under NetworkManager it needs a dispatcher.d script calling busctl on the bgscan property after every connect. Doable, but a lot more moving parts than one nmcli line, and it only buys you a weaker threshold, not silence.
Three more knobs if you want to keep going:
- Power saving off —
wifi.powersave=2in/etc/NetworkManager/conf.d/wifi-powersave.conf, because a card in power save mode sleeps between beacons and adds latency of its own. - Band lock —
nmcli connection modify "<YourSSID>" 802-11-wireless.band akeeps you on 5 GHz (use6GHzwhere available) instead of drifting onto a crowded 2.4 GHz channel. - WiFi 6 off on some Realtek cards —
options rtw89_core disable_11ax=Yin/etc/modprobe.d/, because that driver’s 802.11ax path buffers in ways that show up as micro-stutter.
🖥️ Does this work on every distro?
Yes and no, and the distinction is not about distros at all — it is about who manages your WiFi.
The lock is a NetworkManager feature, so it works wherever NetworkManager is the one holding the WiFi device. That is every mainstream desktop: Ubuntu, Fedora, Debian with a desktop task, Mint, Pop!_OS, openSUSE desktop, Arch, Manjaro, CachyOS, EndeavourOS, Bazzite, Nobara. RHEL 9 and its rebuilds dropped the old ifcfg scripts entirely and are NetworkManager-only. nmcli is the same tool everywhere and 802-11-wireless.bssid is a long-standing property, so the command does not change from one distribution to the next.
Where it does not apply:
- iwd instead of wpa_supplicant. NetworkManager can run iwd as its WiFi backend (
wifi.backend=iwd), and some people switch. In that mode the BSSID lock is a no-op: NetworkManager presents each network as a single BSS with a fabricated BSSID, because roaming belongs to iwd. The equivalent dial there is iwd’s own roaming threshold —RoamThreshold(-70 dBm, 2.4 GHz) andRoamThreshold5G(-76 dBm) in/etc/iwd/main.conf. Check which backend you have withNetworkManager --print-config | grep wifi.backend; the default iswpa_supplicant. - No NetworkManager at all. Server installs and minimal setups often run netplan with systemd-networkd, ifupdown, connman, or iwd standalone. There is no
nmclito call. Under plain wpa_supplicant the equivalent isbssid=in the network block — and you are arguably better off there, because background scanning is off unless you configure it. NetworkManager is the thing that switches it on for you. - The driver. Nothing to do with the distro, but the lock is “highly driver dependent” either way. Mainline
mac80211drivers honour it; some out-of-tree vendor drivers do not.
So: one nmcli command on any NetworkManager desktop, and a different tool entirely if you are on iwd. Two minutes of NetworkManager --print-config will tell you which world you are in.
🏁 TL;DR
One command, and it is the one that matters:
nmcli connection modify "<YourSSID>" 802-11-wireless.bssid "<AP MAC address>"
That tells NetworkManager you are not roaming, which stops it asking the supplicant to background-scan, which stops the radio from leaving your channel every 30 seconds. For cloud gaming over WiFi, that is the whole game. ✅
Article written with the help of an AI
