Why Is My WiFi Slow on Fedora? The Culprit Is Bluetooth (Intel CNVi Antenna Sharing)
In the same room, on the same WiFi: a Windows 10 machine pulled 50 Mbps while my Fedora laptop was stuck at 2-3 Mbps. Driver, signal, channel, background downloads — all clean. Pinging my own modem showed the problem wasn't out on the internet but between my laptop and the modem; the cause was Bluetooth. I proved it with measurements, and I walk through the two fixes that failed and the mistakes I made building the speed test.
There are two laptops at the shop: one is the shop’s computer, running Windows 10; the other is my own personal laptop, running Fedora. Both are in the same room, both connected to the same WiFi network, both close to the modem. But when I ran a speed test, the Windows machine pulled 50 Mbps while my Fedora laptop was stuck at 2-3 Mbps.
Same network, same room, same distance. My line is 50 Mbps, and the Windows machine was getting that full speed — so the line and the modem could deliver 50 without any trouble. The only difference was the operating system. Naturally I thought “something is misconfigured on Linux,” and I was about to start fiddling with the driver and the power-saving settings. Good thing I measured the real speed and latency before touching any settings — because the culprit was nowhere near where I expected, and the problem had nothing to do with WiFi settings.
In this post I walk through, step by step, how I found the source of the problem: which command I ran, what it returned, and what I concluded from it. I also include my two wrong hypotheses and the mistakes I made while building the speed test — because that is the most instructive part.
My System / Test Environment#
| Component | Value |
|---|---|
| Model | ASUS Zenbook 14 OLED (UX3405CA) |
| CPU | Intel Core Ultra 9 285H |
| Wireless card | Intel Arrow Lake CNVi WiFi (iwlwifi driver) |
| OS | Fedora Linux 44 (KDE Plasma) |
| Kernel | 7.1.3-201.fc44.x86_64 |
| Session | Wayland |
| Modem | TP-Link TD-W9970 (300 Mbps Wireless N, VDSL2) |
| Line speed | ~50 Mbps |
The two most important rows here are Wireless card and Modem. As you will see at the end, the whole problem comes from the combination of these two.
Step 1: Hardware first — is the card detected correctly?#
I started from the very beginning. What was the card, and which driver was it running?
# Show the network controller and the kernel driver it uses
lspci -nnk | grep -A3 -i 'network controller'
00:14.3 Network controller [0280]: Intel Corporation Arrow Lake CNVi WiFi [8086:7740]
DeviceName: WLAN
Subsystem: Intel Corporation Device [8086:00e4]
Kernel driver in use: iwlwifi
The card is detected correctly and the official Intel iwlwifi driver is loaded. No problem so far. I looked at the interfaces too:
nmcli device status
DEVICE TYPE STATE CONNECTION
wlo1 wifi connected www.ulvikuyum.com.tr
lo loopback connected (externally) lo
p2p-dev-wlo1 wifi-p2p disconnected --
One thing worth pointing out here: there is no ethernet in the list. This laptop has no wired port to begin with — so “just plug in a cable and be done with it” was never an option, and I had to solve the problem over WiFi.
The
wlo1in this output is the name of my wireless interface. Yours might bewlp2s0,wlan0, etc. Wherever you seewlo1in the rest of this post, use your own interface name — whatever thewifiline innmcli device statusshows.
Step 2: Is the signal weak, or the link rate low?#
The obvious first guess: maybe the signal is bad, maybe the card negotiated a low rate with the modem.
# The connection's live status: signal strength and negotiated rate
iw dev wlo1 link
Connected to 5c:63:bf:16:e8:f5 (on wlo1)
SSID: www.ulvikuyum.com.tr
freq: 2437.0
signal: -55 dBm
rx bitrate: 130.0 MBit/s MCS 15
tx bitrate: 130.0 MBit/s MCS 15
This output was the exact opposite of what I expected:
signal: -55 dBm— that is a good signal. (Roughly: -50 excellent, -60 good, -70 usable, -80 bad.)130.0 MBit/s— the card negotiated 130 Mbit/s with the modem. Far more than enough to carry my 50 Mbps line.freq: 2437.0— the 2.4 GHz band, channel 6.
So on paper the connection is rock solid. I dug deeper and looked at the error counters:
# Retransmit and error counters
iw dev wlo1 station dump
signal: -55 [-59, -55] dBm
tx bitrate: 130.0 MBit/s MCS 15
rx bitrate: 130.0 MBit/s MCS 15
tx retries: 0
tx failed: 0
tx retries: 0 and tx failed: 0. Not a single packet retransmitted, not a single send failed. Nothing looked wrong on the radio side — the part of the card that actually sends and receives the signal.
Step 3: Could power saving be putting WiFi to sleep?#
A known headache on Linux: on laptops the WiFi card goes to sleep to save power and adds latency. I checked:
iw dev wlo1 get power_save
Power save: off
It was off on my machine — but the reason is that I had already disabled WiFi power saving on this laptop earlier. So I had ruled this out from the start; on your system it may show Power save: on.
If it is on and you are seeing latency, here is how to turn it off — temporarily (comes back on reboot):
sudo iw dev wlo1 set power_save off
To make it persistent, add a NetworkManager rule:
# Keep WiFi power saving off on every boot
echo -e '[connection]\nwifi.powersave = 2' | sudo tee /etc/NetworkManager/conf.d/wifi-powersave-off.conf
sudo systemctl restart NetworkManager
wifi.powersavevalues:2= off,3= on (default). After changing it, confirm withiw dev wlo1 get power_save.
On my machine it was already off, so this possibility was ruled out; the problem was elsewhere.
Step 4: Is something downloading in the background?#
Maybe an update, a backup, or a sync was using my bandwidth. First I looked at which programs had network connections:
# Active network connections and which process they belong to
sudo ss -tunp | grep ESTAB
The list had the browser, the code editor, and normal desktop programs — no unexpected downloader. Then I made the real measurement: how many bytes per second are actually flowing through the interface?
# Measure the traffic over 5 seconds (while idle)
r1=$(cat /sys/class/net/wlo1/statistics/rx_bytes)
sleep 5
r2=$(cat /sys/class/net/wlo1/statistics/rx_bytes)
echo "Download: $(( (r2-r1)/5/1024 )) KB/s"
Download: 2 KB/s
Only two KB per second. So nothing was downloading while idle. Another possibility ruled out.
Let me share a habit here: a program “looking active” and a program “actually using bandwidth” are two different things. Instead of looking at the process list and guessing, reading the interface counters gives a definite answer.
Step 5: The turning point — I pinged my own modem#
At this point I had a clean signal, a solid link rate, power saving off, and an idle network. But the internet was still slow. A question occurred to me:
“Is the problem really far away, out on the internet? Or does it start right here, between my laptop and the modem?”
The cleanest way to separate the two: ping only my own modem, without touching the internet at all. The modem is a few steps away; there’s no internet between us, only WiFi.
# Only the modem (the internet is not involved)
ping -c 20 -i 0.2 192.168.1.1
20 packets transmitted, 20 received, 0% packet loss, time 3820ms
rtt min/avg/max/mdev = 5.699/144.216/319.984/82.170 ms
There it was. Let’s read these numbers:
min 5.699— at best 5.7 msavg 144.216— but 144 ms on averagemax 319.984— and at times 320 msmdev 82.170— the deviation is very high, so the values swing a lot from one measurement to the next
Reaching a modem a few steps away was taking up to 320 milliseconds. On a healthy wireless connection this should be a steady 1-5 ms.
The internet is not involved in this measurement at all. So the problem is not my ISP, not my VDSL line, not some distant server. The problem is right here, in the wireless link between my laptop and the modem.
To see the pattern, I ran a longer measurement:
# One ping per second for 60 seconds, print only the times
LC_ALL=C ping -c 60 -i 1 192.168.1.1 | grep -o 'time=[0-9.]*'
The leading
LC_ALL=Cmatters. My desktop is in Turkish, sopinglocalizes its output and the time field becomeszaman=instead oftime=; withoutLC_ALL=C,grepfinds nothing and the command returns empty. (I learned this on my first try.) On an English system it is a harmless no-op.
Simplified, the table looked like this:
1.9 3.8 2.1 88.2 47.1 2.2 2.4 46.4 283.0 61.2
7.9 4.3 2.1 1.4 2.2 1.9 34.3 2.4 4.2 7.7
1.4 1.3 1.4 1.9 1.8 1.2 27.3 13.1 16.4 2.4
The pattern is very clear: mostly 1-4 ms (perfectly healthy), then suddenly jumping to 30-280 ms, then back to normal.
This was valuable information. Because:
- If it were a driver-sleep problem, the spikes would come at regular intervals (every X seconds).
- If the signal were weak, the values would be consistently high.
My pattern was neither: sudden stalls at irregular intervals. That meant something else was using the same frequency intermittently.
Step 6: Is the channel crowded?#
2.4 GHz is the band where everyone piles up. I looked at the nearby networks and which channels they were on:
# Nearby networks, their channels and signal strengths
nmcli -f BSSID,SSID,CHAN,SIGNAL device wifi list --rescan yes
The rows on channel 6 (my channel) from the output:
BSSID SSID CHAN SIGNAL
5C:63:BF:16:E8:F5 www.ulvikuyum.com.tr 6 85
CC:BA:BD:B7:98:56 FiberHGW_TP9850 6 42
5C:7D:AE:F4:E3:85 SUPERBOX_Wi-Fi_9665 6 20
I counted 19 neighboring networks in total. Channel 6 was crowded, and this was lowering my speed somewhat.
But in other scans another network showed up on channel 6: Ulvi Kuyum Müşteri — the shop’s customer network, my own second network. And its signal was very strong. “Are my own two networks interfering with each other?” I worried, and searched specifically for it:
# Is the customer network a separate device, or the modem's second network?
nmcli device wifi rescan
nmcli -f BSSID,SSID,CHAN,SIGNAL device wifi list | grep -i ulvi
52:63:BF:16:E8:F4 Ulvi Kuyum Müşteri 6 89
5C:63:BF:16:E8:F5 www.ulvikuyum.com.tr 6 82
When I looked at the two networks’ hardware addresses (BSSIDs), I relaxed:
Main network : 5C:63:BF:16:E8:F5
Customer network : 52:63:BF:16:E8:F4
Both carry the same base address (63:BF:16:E8). So the customer network is not a separate device — it is a second network the same modem creates. Because both use the same hardware, the modem itself schedules which one transmits when; so unlike two separate devices, they do not corrupt each other’s signal.
But let me note this too: if a customer connects to that network and watches video, they share the same radio and the same VDSL line with me. So even though it is not a separate device, it can still affect my speed. Channel crowding and the customer network are real factors — but as you will see shortly, they were not the main cause.
Step 7: A likely cause — the card sharing the antenna with Bluetooth#
I went back to the output from Step 1. There was one word in it: CNVi.
Intel Corporation Arrow Lake CNVi WiFi
CNVi is Intel’s new-generation integrated wireless architecture. In this design, WiFi and Bluetooth share the same physical antenna. Because both work at 2.4 GHz, they cannot transmit at the same time; they have to take turns using the antenna.
I checked Bluetooth’s state:
bluetoothctl show | grep Powered
bluetoothctl devices Paired
Powered: yes
Bluetooth was on. And the devices Paired command returned nothing — meaning I did not have a single paired device.
This explained the irregular spike pattern I saw in Step 5: even though I had no paired device, Bluetooth was on, so every now and then it could be using the antenna and pushing WiFi back in the queue. The fact that the spikes were random rather than regular fit this exactly.
Step 8: The first real test — turn off Bluetooth, measure the speed#
I stopped guessing and measured. The way to turn Bluetooth off temporarily:
# Turn Bluetooth off temporarily (not permanent, comes back on reboot)
sudo rfkill block bluetooth
# To turn it back on
sudo rfkill unblock bluetooth
For the speed measurement I downloaded a file and looked at how many bytes actually flowed:
# Download for 10 seconds, print the bytes downloaded and the time
curl -m 10 -o /dev/null -s -w '%{size_download} bytes / %{time_total} s\n' \
http://cachefly.cachefly.net/100mb.test
I measured three times each with Bluetooth on and off:
Bluetooth ON : 16.4 8.8 39.9 Mbps
Bluetooth OFF : 40.3 39.5 44.0 Mbps
The difference was obvious. But I did not stop here — because with Bluetooth on, one measurement came out 39.9. So Bluetooth was not slowing the connection down constantly, it was crashing it intermittently. Three measurements were not enough to prove it.
Step 9: A hypothesis that failed — “what if I give WiFi priority?”#
A reasonable idea occurred to me: instead of turning Bluetooth off entirely, what if I told the driver “give WiFi priority, don’t yield to Bluetooth”?
The iwlwifi driver has a setting for exactly this. First I looked at its current value:
# List all of the driver's parameters
for p in /sys/module/iwlwifi/parameters/*; do
echo "$(basename $p) = $(cat $p)"
done
11n_disable = 0
bt_coex_active = Y
power_save = N
uapsd_disable = 3
...
bt_coex_active = Y — so the coordination that makes WiFi and Bluetooth take turns on the antenna is on. I tried turning it off by setting it to N. But first I checked whether the setting could be changed while the system was running:
ls -l /sys/module/iwlwifi/parameters/bt_coex_active
-r--r--r--. 1 root root 4096 Jul 18 13:40 /sys/module/iwlwifi/parameters/bt_coex_active
-r--r--r-- — so it is read-only. Changing this setting required reloading the driver from scratch.
I read exactly what this setting does, straight from the driver’s own description:
modinfo iwlwifi | grep bt_coex
parm: bt_coex_active:enable wifi/bt co-exist (default: enable) (bool)
Then I reloaded the driver with this setting:
# Write the setting
echo 'options iwlwifi bt_coex_active=0' | sudo tee /etc/modprobe.d/99-btcoex.conf
# Reload the driver (WiFi drops for a few seconds)
sudo modprobe -r iwlmld
sudo modprobe -r iwlwifi
sudo modprobe iwlwifi
# Verify
cat /sys/module/iwlwifi/parameters/bt_coex_active
N
The setting took. I measured — and it did not work:
| Setting | Download (3 measurements) | Ping avg / max |
|---|---|---|
Bluetooth on, bt_coex_active=Y | 10.9 / 11.2 / 40.4 | 14 ms / 142 ms |
Bluetooth on, bt_coex_active=N | 10.1 / 21.1 / 32.9 | 55 ms / 354 ms |
The speed did not improve, and the ping got even worse.
I understood the reason afterward; it actually made sense. The antenna is physically single. bt_coex_active is not a switch that splits that antenna in two — think of it as a traffic light that lets WiFi and Bluetooth use the antenna in turn, without colliding. Turning it off does not take the second car (Bluetooth) off the road; it only removes the traffic light. Bluetooth keeps using the antenna, but now without waiting its turn. The result: a collision — that is, things get even worse.
I reverted the setting:
sudo rm /etc/modprobe.d/99-btcoex.conf
sudo modprobe -r iwlmld && sudo modprobe -r iwlwifi && sudo modprobe iwlwifi
Step 10: Setting up the measurement correctly (and the mistakes I made along the way)#
I had three measurements per condition, but the results were very jumpy. In an environment this variable, saying “I proved it with three measurements” would not be honest. So I decided to set up a proper test.
I made two design decisions:
1. A measurement that flips the order every round. If I measured all “Bluetooth on” first and all “Bluetooth off” later, it would be wrong: if the neighbors’ traffic changed during those two minutes, I could wrongly pin the difference on Bluetooth. Instead I measured both in every round and flipped the order each round (on first on odd rounds, off first on even rounds).
2. A fixed duration, not a fixed file size. If I downloaded a fixed size (say 20 MB), the measurement would take 4 seconds in a fast condition and 20 seconds in a slow one — meaning I’d be sampling the two conditions over windows of different length. Instead I downloaded for exactly 10 seconds each time and computed the speed like this: speed = bytes downloaded ÷ time elapsed.
But the part I really want to tell you is this: I had to rebuild this test four times, because in each attempt I noticed a flaw in my own method.
Mistake 1: I was losing the ping summary#
I ran a ping at the same time as the download and stopped the ping once the download finished. But ping prints its summary line (rtt min/avg/max) only when it receives a SIGINT signal; I was stopping it with kill, which sends SIGTERM. Result: in the measurements that finished quickly, the ping summary was lost before it was ever printed.
This wasn’t an innocent mistake: the measurements that finished quickly mostly fell into the “Bluetooth off” condition. So this error damaged one of the two conditions I was comparing more than the other — exactly the kind of thing that skews a result. The fix:
kill -INT $ping_pid # kill -INT, not kill
Mistake 2: The measurement server rate-limited me#
For the measurements I was using Cloudflare’s speed test server. After I requested a 50 MB file dozens of times back-to-back, the server started throttling me and, instead of the file, began returning 1 byte to my requests:
attempt 1: 1 byte / 0.1 s
attempt 2: 1 byte / 0.1 s
attempt 3: 1 byte / 0.1 s
These measurements were being recorded as “0 Mbps.” If I had not noticed, my averages would have been complete garbage. I tried alternative servers:
curl -m 10 -o /dev/null -s -w '%{size_download} bytes / %{time_total} s\n' \
http://cachefly.cachefly.net/100mb.test
And I separately confirmed the server itself was not the bottleneck — because a slow server would hide the difference. I measured five times with Bluetooth off:
44.8 45.4 44.5 43.6 44.7 Mbps
The server could deliver 45 Mbps, so it exceeded my WiFi ceiling. It was fine for measuring.
Mistake 3: My filter was deleting the slowest measurements#
This was the most dangerous mistake: the error it produced leaned toward confirming my own hypothesis.
To weed out the measurements the server refused (the 1-byte ones), I had written a rule: “if less than 5 MB came in, count that measurement invalid and retry.” But then I noticed this in the logs:
(invalid sample: 4092677 bytes / 10.000376 s -- retrying)
This measurement downloaded 4 MB in exactly 10 seconds. So the server hadn’t refused it — the connection had genuinely dropped to 3.3 Mbps. This wasn’t broken data; it was the very thing I was trying to measure.
So my rule was throwing out the slowest measurements as “broken” and replacing them with fresh ones. And every one of those discarded measurements fell into the “Bluetooth on” rounds. In effect I was making the “Bluetooth on” condition look better than it really was — I was unknowingly deleting the very data that weakened my own hypothesis.
The correct rule was: a measurement is invalid only if it ended before the 10-second window filled (a server refusal comes back in 0.08 seconds anyway). If the window ran the full 10 seconds, the measurement is valid no matter how many bytes came in.
The rule that came out of this: when you test a hypothesis, you also have to test the rule that filters your data. The data you throw out as “broken” may be exactly the answer you’re looking for.
Step 11: The large-scale test#
After four attempts the method settled. So that anyone hitting the same problem can run it on their own machine, here is the simplified version:
# 6 rounds: measure with Bluetooth on and off in each round
for i in 1 2 3 4 5 6; do
echo "--- round $i ---"
sudo rfkill unblock bluetooth; sleep 5
echo -n " BT on : "
curl -m 10 -o /dev/null -s -w '%{size_download} bytes / %{time_total} s\n' \
http://cachefly.cachefly.net/100mb.test
sudo rfkill block bluetooth; sleep 5
echo -n " BT off : "
curl -m 10 -o /dev/null -s -w '%{size_download} bytes / %{time_total} s\n' \
http://cachefly.cachefly.net/100mb.test
done
sudo rfkill unblock bluetooth # turn Bluetooth back on
I ran this for 12 rounds, measuring download + upload + idle ping + ping under load. A total of 34 valid measurements, no discarded samples, all measurement windows exactly 10.00 seconds (so both conditions were measured over the exact same duration).
Result: Download speed#
| Condition | Median | Mean | Min | Max |
|---|---|---|---|---|
| Bluetooth on | 15.9 | 22.2 | 6.5 | 45.5 |
| Bluetooth off | 45.4 | 43.3 | 31.4 | 46.3 |
But the telling thing is not the mean, it is the distribution of the values:
Bluetooth on : ▁▁▁▁▁▁▁▁████
Bluetooth off : ████████████
█ = above 30 Mbps ▁ = below 30 Mbps (crashed)
With Bluetooth off, 12 of 12 measurements are at full speed. With it on, 8 of 12 crashed, only 4 were at full speed.
So Bluetooth is not halving my speed — it is making it unpredictable. This exactly explains the complaint I had: the internet was sometimes very slow, sometimes normal, and I could never get a consistent result.
Result: Comparing each round on its own#
Comparing raw averages was risky, because the environment kept changing during the test (neighbor traffic, the customer network). Instead, for each round I put the “on” and “off” values — measured a few seconds apart — side by side. If the environment shifts in those few seconds, it affects both; so the difference between them still stays meaningful:
round BT_ON BT_OFF diff
1 6.5 38.5 +32.0
2 44.4 31.4 -13.0
3 13.0 45.1 +32.0
4 15.9 46.3 +30.4
5 32.0 41.8 +9.8
6 9.6 42.4 +32.8
7 7.4 45.7 +38.3
8 45.1 45.9 +0.8
9 15.1 45.7 +30.6
10 45.5 46.2 +0.7
11 15.9 46.0 +30.0
12 16.0 44.9 +28.9
----------------------------------------------
Bluetooth off faster : 11 rounds
Bluetooth on faster : 1 round
Median difference : +30.2 Mbps
In 11 of the 12 rounds “Bluetooth off” came out faster. I’m deliberately leaving round 2 in the table: there, the opposite happened — Bluetooth was faster while on (44.4 vs 31.4). Deleting that contrary example would have suited me, but it proves exactly what I want to show: Bluetooth’s effect shows up now and then, not in every measurement.
Result: Latency (what you actually feel)#
Ping under load (peak value)
Bluetooth on : median 397.8 ms (worst 1219 ms)
Bluetooth off : median 37.8 ms (worst 127 ms)
A tenfold difference. The ping climbing to 400 ms while downloading — and once even 1219 ms — is exactly this: the numerical form of the freezes I got in video calls and the pages that “just wouldn’t load.”
Result: Upload is barely affected#
Upload
Bluetooth on : median 12.7 Mbps
Bluetooth off : median 14.6 Mbps
This surprised me: Bluetooth cuts the download speed by 65% but barely touches the upload speed. I won’t claim the exact reason, but the plausible explanation is: for a download, I have to catch the moment the other side sends data to me; if the antenna is on Bluetooth right then, I miss it. For an upload, I decide when to send, so I send when the antenna is free.
Step 12: The second failed hypothesis — power_scheme#
While examining the driver I found one more setting. iw said “power save off,” but the driver had a separate power scheme at its own firmware level:
cat /sys/module/iwlmld/parameters/power_scheme
2
1 = active (no power saving), 2 = balanced (default). I tried this too:
echo 'options iwlmld power_scheme=1' | sudo tee /etc/modprobe.d/99-ps.conf
sudo modprobe -r iwlmld && sudo modprobe -r iwlwifi && sudo modprobe iwlwifi
The result — this did not work either:
| Condition | Median download | Crash rate |
|---|---|---|
power_scheme=1, Bluetooth on | 16.4 Mbps | 60% |
power_scheme=1, Bluetooth off | 45.2 Mbps | 0% |
With Bluetooth on it still crashed. And the idle ping got worse (median 4.9 ms → 45.3 ms). I reverted this setting too:
sudo rm /etc/modprobe.d/99-ps.conf
sudo modprobe -r iwlmld && sudo modprobe -r iwlwifi && sudo modprobe iwlwifi
Both hypotheses failed. One option remained: going back to the traffic-light analogy from Step 9 — since the antenna is physically single, the fix isn’t to fiddle with the light, but to take the second car off the road entirely, that is, Bluetooth.
The Fix#
For me this was an easy call: when I measured, I’d already seen that I didn’t have a single paired Bluetooth device. So Bluetooth was giving me nothing and only slowing WiFi down; I had nothing to lose by turning it off.
# Turn Bluetooth off and keep it from starting at boot
sudo systemctl disable --now bluetooth
The command gave this output:
Removed '/etc/systemd/system/dbus-org.bluez.service'.
Removed '/etc/systemd/system/bluetooth.target.wants/bluetooth.service'.
Verification:
systemctl is-enabled bluetooth
systemctl is-active bluetooth
disabled
inactive
Is stopping the service enough, or should I also turn off the radio (the antenna hardware)?#
Here I hesitated. systemctl disable only stops the Bluetooth software (the bluetoothd service). But the thing that actually gets in WiFi’s way isn’t the software — it’s the radio, meaning the part of the card that physically sends and receives the signal. So did stopping the service actually stop the radio? I checked:
rfkill list bluetooth
0: hci0: Bluetooth
Soft blocked: no
Hard blocked: no
So at the hardware level the radio still looked on. “Then I need to turn the radio off by hand too, with rfkill block,” I thought — but instead of assuming, I measured. With the service off but rfkill still unblocked, I measured the speed six times:
44.4 44.6 43.5 45.5 45.8 42.5 Mbps
All six at full speed. The idle ping had recovered too:
rtt min/avg/max/mdev = 1.176/4.304/20.582/5.230 ms
So rfkill block isn’t needed; stopping the service is enough. Apparently what was slowing WiFi down was the constant background scanning and advertising the idle Bluetooth service kept doing. Once the service stops, the radio effectively stops transmitting too, and WiFi breathes.
If one day I want to connect headphones or a wireless mouse, Bluetooth comes back with a single line; none of my settings are deleted:
sudo systemctl enable --now bluetooth
If you want to turn it off only for the moment rather than permanently (Bluetooth comes back on its own after a reboot):
sudo rfkill block bluetooth
But the real root cause is not Bluetooth, it’s the modem#
Here I have to be honest. Turning off Bluetooth is not a solution, it is a workaround. The real problem is one layer deeper.
My modem is a TP-Link TD-W9970, and this device broadcasts only 2.4 GHz; it has no 5 GHz band. I have a reason for sticking with it: there is no fiber infrastructure in my area, and when I want to change my IP address I can get a fresh one in 5-10 seconds without even resetting the modem — I don’t want to give up that convenience. Luckily my laptop’s card supports much more than 2.4 GHz:
# The frequency bands the card supports
iw phy "$(basename /sys/class/ieee80211/*)" info | grep -E 'Band [0-9]:'
Band 1:
Band 2:
Band 4:
I did not write
phy0here, because the wireless device’s name is not alwaysphy0— when you reload the driver the number increases (at the end of these tests mine had becomephy4).$(basename /sys/class/ieee80211/*)finds the name automatically.
Band 1 = 2.4 GHz, Band 2 = 5 GHz, Band 4 = 6 GHz. So my card supports 5 and 6 GHz, but since the modem doesn’t offer those bands I can’t use them; I’m stuck on the 2.4 GHz that everyone is crammed into.
Here’s why that’s critical: the WiFi/Bluetooth antenna-sharing problem only appears on 2.4 GHz. Bluetooth always works at 2.4 GHz; if WiFi could move up to 5 GHz, the two would be on different frequencies and wouldn’t get in each other’s way even while sharing the same antenna.
I know this from my own experience: on fiber modems that have 5 GHz, I’ve never hit this problem. Same laptop, same Bluetooth, same Fedora — but no problem. Because there WiFi moves up to 5 GHz; since Bluetooth stays at 2.4 GHz, the two run on separate frequencies and don’t clash.
So the picture is this:
| Layer | Status |
|---|---|
| My internet line | ~50 Mbps — not the problem |
| My modem | 2.4 GHz-only VDSL2 — the real constraint is here |
| My card | supports 5 and 6 GHz but can’t use them |
| Bluetooth | shares the antenna on 2.4 GHz — this is the felt problem |
As long as I’m on this modem, my practical fix is to keep Bluetooth off. The real, permanent fix would be to add a modem or access point that supports 5 GHz; that would get rid of both the 2.4 GHz crowding and the antenna-sharing problem in one move.
6 rules for debugging a slow network#
1. “Same network but one device is slow” does not mean the problem is on the internet. My problem wasn’t far away — it was in the wireless link between my laptop and the modem; and I could only see it with a measurement that never touched the internet (pinging my own modem). That single command was the most useful thing in the whole diagnosis.
2. A good signal and link rate do not mean the connection is good. I had -55 dBm signal, 130 Mbit/s link, 0 error counters — all the indicators were perfect. But what told the truth wasn’t these indicators — it was how the latency was distributed.
3. The average can mislead you. With Bluetooth on my average was 22 Mbps; that looks like “half speed.” But the reality was: 67% of the measurements crashed, 33% were at full speed. That was the reason for my “sometimes slow” complaint, and I only saw it by looking at the distribution.
4. When testing a hypothesis, test the rule that filters your data too. The samples I threw away as “broken measurements” were the very answer I was looking for — and they were all concentrated in one group. If I had not noticed, I would have published a result produced by my own filter as “proof.”
5. A failed hypothesis is a result too. My bt_coex_active=0 and power_scheme=1 attempts failed. But understanding why they failed (the antenna is physically single; removing the traffic light does not take the car off the road) opened the way to the correct solution.
6. Don’t change anything before you measure. My first instinct was to fiddle with driver settings. If I had, I would have searched in the wrong place for days — because there was nothing wrong with the driver.
Comments
Comments use your GitHub account. Clicking “Show” loads Giscus (giscus.app).