WiFi Bufferbloat Collapses the Connection When Streaming to a LAN Device (Expo, Steam Link)
Internet felt "unstable" only in specific situations — running an Expo dev server for a phone to connect to, or streaming games to a phone via Steam Link. Regular browsing was fine most of the time. Turned out to be classic bufferbloat: no active queue management on the wireless interface, so a big sustained transfer buried everything else (pings, DNS, keepalives) behind it.
Note
This machine also hit a second, unrelated WiFi instability during the same investigation — see WiFi Card Drops Connection Under Load Due to PCIe Power Management. That bug persisted even after this bufferbloat fix was applied, so don't assume fixing bufferbloat alone rules out further WiFi instability.
Environment
- OS: Ubuntu (kernel
6.17.0-1032-oem) - WiFi chipset: MediaTek MT7925E (
mt7925edriver, PCIe) - Network manager: NetworkManager
- Symptom reproduced with: Expo (
expo start, Metro bundler push to phone), Steam Link (game streaming to phone over LAN) - Not specific to this chipset or these two apps — see Root Cause
Problem
Internet "randomly" became unstable — high ping, stalls, occasional full disconnects — but only during specific activities:
- Running
expo startand connecting a phone to it for live-reload development - Streaming a game to a phone with Steam Link
Once the dev server was stopped or the stream ended, the connection went back to normal within seconds. This on/off correlation was the first real clue — a flaky router or ISP wouldn't care whether Expo was running.
Initial ping tests to the gateway and to 8.8.8.8 looked mostly fine in isolation:
$ ping -c 10 8.8.8.8
...
10 packets transmitted, 10 received, 0% packet loss
rtt min/avg/max/mdev = 43.582/57.192/72.442/7.005 ms
The instability only showed up under load, which is why a quick manual ping check kept looking "fine" — the bug is load-dependent, not constant.
Root Cause
Reproduced live by running ping -D against both the gateway and 8.8.8.8 in the
background while starting Expo / Steam Link, and watching journalctl -k -f at the same
time. Two things were checked in parallel: whether the WiFi was actually
disconnecting/reassociating at the driver level, and what the latency was doing.
sequenceDiagram
participant Phone
participant Laptop as Laptop (wlp4s0)
participant Radio as WiFi Queue (noqueue)
participant Router
participant Internet
Phone->>Laptop: Connect (Expo bundle / Steam Link stream)
Laptop->>Radio: Push large sustained transfer
Note over Radio: Queue has no size limit,<br/>no per-flow fairness
Radio-->>Radio: Queue fills up (bufferbloat)
Laptop->>Radio: Ping / keepalive packet
Note over Radio: Stuck behind the bulk transfer
Radio--xRouter: Delivered late (500ms-1200ms) or dropped
Router--xInternet: Everything sharing the link feels it
Note over Phone,Internet: App's own quality/timeout logic<br/>gives up → stream drops entirely
Findings, in order:
- No deauth/reassociation during the actual bufferbloat collapse.
journalctl -k -fshowed zero WiFi-level disconnect events during the worst latency spikes. This ruled out "flaky driver keeps dropping the radio link" as the mechanism for this specific symptom (a separate, unrelatedmt7925edeauth/reassoc issue was also present in this environment, traced to stale firmware — but it wasn't what caused the Expo/Steam Link drops). -
Latency spiked identically on the gateway hop and on
8.8.8.8at the same moments, proving the bottleneck was the local WiFi link, not the ISP or anything past the router: -
The interface's queueing discipline (qdisc) was
noqueue:noqueuemeans no active queue management at all — packets queue up in the driver with no size cap and no fairness between flows. A large sustained transfer (Expo's bundle push, Steam Link's continuous video stream) fills that queue completely. Every other packet sharing the interface — including ICMP pings, DNS, TCP ACKs, and Steam Link's own control/keepalive traffic — queues up behind it and gets delayed by however long it takes to drain the backlog. This is the textbook definition of bufferbloat. -
Why it only shows up with Expo/Steam Link and not normal browsing: ordinary web traffic is bursty and asymmetric — it doesn't sustain enough continuous throughput to fill an unbounded queue. Expo's bundle transfer and, especially, Steam Link's continuous video stream do sustain load, so they're what exposes the problem.
-
Why the underlying WiFi chipset/driver matters less than it seems: this is not a
mt7925e-specific bug. WiFi interfaces on Linux default tonoqueuebecause themac80211stack does its own internal per-station/per-TID queueing for 802.11e QoS and frame aggregation — stacking a second dumbtcqueue on top used to be considered redundant. The gap is thatmac80211's internal queueing does frame scheduling/aggregation, not latency-bounded active queue management (AQM). Wired Ethernet interfaces, by contrast, ship withfq_codel-family defaults precisely because they lack that internal intelligence and need it at thetclayer. Any Linux laptop onnoqueueWiFi sharing bandwidth with a sustained-throughput LAN app can hit this.
Fix
Temporary (until reboot / next interface reconnect)
Replace the interface's qdisc with fq_codel (Fair Queuing + Controlled Delay):
- Fair Queuing: splits traffic into per-flow queues, so one hungry flow (the Steam Link stream) can't starve everything else (ping, DNS, the stream's own keepalives) sharing the same interface.
- CoDel: actively watches queue delay and drops/marks packets once delay creeps up, keeping latency bounded instead of letting the queue grow unchecked.
Verify it applied:
$ tc qdisc show dev wlp4s0
qdisc fq_codel 8001: root refcnt 2 limit 10240p flows 1024 quantum 1514 target 5ms interval 100ms memory_limit 32Mb ecn drop_batch 64
This alone took a Steam Link session from dropping after ~10 seconds to running for 20+ minutes uninterrupted (stopped voluntarily, not by a failure).
Persistent (survives reboot, sleep/resume, WiFi reconnects, roaming)
tc qdisc settings don't survive an interface going down and back up, and WiFi
interfaces cycle through that constantly (sleep/resume, roaming, reconnects). Reapply it
automatically via a NetworkManager dispatcher script, keyed on the interface name, not
the SSID — so it applies no matter which WiFi network you connect to.
Create /etc/NetworkManager/dispatcher.d/99-fq_codel:
#!/bin/sh
# Apply fq_codel to wlp4s0 to fix WiFi bufferbloat (Steam Link / Expo stalls).
IFACE="$1"
ACTION="$2"
if [ "$IFACE" = "wlp4s0" ] && { [ "$ACTION" = "up" ] || [ "$ACTION" = "connectivity-change" ]; }; then
/sbin/tc qdisc replace dev wlp4s0 root fq_codel
fi
Then set correct ownership and permissions — NetworkManager silently ignores dispatcher
scripts that aren't root:root and executable:
sudo chown root:root /etc/NetworkManager/dispatcher.d/99-fq_codel
sudo chmod 755 /etc/NetworkManager/dispatcher.d/99-fq_codel
Verify it actually fires by cycling the radio and checking the qdisc again (should show
fq_codel again without running the tc command manually):
Swap wlp4s0 for your own interface name (ip -br addr to find it) if different.
Prevention / How to Recognize This Class of Bug Again
flowchart TD
A["'Internet feels unstable'"] --> B{Constant or only\nunder specific activity?}
B -->|Constant, even idle| C[Check ISP / router / signal strength\nnot this bug]
B -->|Only during sustained LAN transfer\ne.g. streaming, dev server, backups, torrents| D[Suspect WiFi bufferbloat]
D --> E["tc qdisc show dev <iface>"]
E -->|noqueue or pfifo_fast| F[Root cause confirmed]
E -->|already fq_codel/cake| G[Look elsewhere:\nchannel congestion, driver bug, ISP]
F --> H["tc qdisc replace dev <iface> root fq_codel"]
H --> I[Persist via NetworkManager\ndispatcher.d script]
- Recognize the pattern: instability that correlates with a specific sustained-load activity (game streaming, dev server pushing builds to a device, large uploads/backups, torrenting) rather than being constant, is a strong signal for bufferbloat rather than a hardware/ISP fault.
- Quick diagnostic:
tc qdisc show dev <interface>— if it saysnoqueueorpfifo_fastand you're seeing load-correlated instability, this is very likely it. - Confirm before fixing: reproduce with a background
ping -D <gateway>andping -D 8.8.8.8running while the load-generating app runs. If both spike together at the same moments, the bottleneck is local (your queue), not the ISP — this also rules out wasting time on router/ISP troubleshooting. - This fix is scoped to the interface, not the network — it applies regardless of
which WiFi SSID you connect to, since the dispatcher script matches on
wlp4s0, not a connection profile. - What this fix does not solve: physical-layer capacity. If the WiFi channel itself
is congested (many overlapping neighboring networks) or the link rate is low, fq_codel
manages the resulting congestion gracefully instead of collapsing, but it can't create
bandwidth that isn't there. Persistent jitter even with fq_codel active is a sign to
also check channel congestion (
nmcli -f SSID,SIGNAL,CHAN dev wifi list) and consider moving the AP to a less congested channel.
Beyond This Fix: Bufferbloat Further Upstream (Router / WAN Link)
Fixing the local WiFi interface's qdisc only manages queuing at that hop. The same
class of problem can also exist further out — the router's WAN uplink queue, the ISP's
modem, or within the ISP's own network — and a local fq_codel fix on the laptop won't
touch that.
A run of a bufferbloat test (via the tools linked from bufferbloat.net) came back Grade C (~170ms added latency under load), meaning load on the WAN link adds meaningfully to ping — the same underlying signature as the WiFi bufferbloat above, just one or more hops further out.
Note
This hasn't been root-caused yet — the directions below are where to look next, not a confirmed fix. The rest of this article covers the local WiFi fix only; treat WAN-side bufferbloat as a separate, still-open problem.
- Isolate direction first. Most bufferbloat test tools grade upload and download separately. Upload-side bufferbloat is usually the router pushing packets to the modem faster than the real uplink can drain them; download-side is usually a queue on the ISP's own equipment (modem/CMTS/ONT) filling up before packets even reach the router.
- Retest over a wired connection to the router (bypass WiFi entirely) to confirm the WAN-level grade isn't partly a leftover of the already-fixed WiFi issue, before assuming the router/WAN link itself is the culprit.
- Smart Queue Management (SQM) on the router is the standard fix for this class:
shape traffic at the router to slightly under the real WAN bandwidth (both directions)
using a modern AQM (
cakeorfq_codel), so the router's own queue absorbs and manages the backlog instead of an unmanaged one further upstream. On OpenWrt, that's thesqm-scriptspackage (cakeqdisc); on pfSense/OPNsense, the Traffic Shaper/Limiters with CoDel; some consumer routers expose it as "Smart Queue Management" or "Adaptive QoS" in their UI. - If SQM doesn't close the gap, the queuing may be happening on the ISP's own equipment, which a home router can't manage — that points toward the ISP itself rather than anything fixable locally.
Further Reading
- Bufferbloat.net Projects — background on
bufferbloat, the tools/projects behind
fq_codel/cake, and ongoing work on the problem beyond this one interface-level fix.
Related Articles
- WiFi Card Drops Connection Under Load Due to PCIe Power Management — a separate, unrelated WiFi instability found on the same machine during this investigation; don't assume this fix rules it out.