Why your transfer stalls at 3% packet loss
2026-09-18
Every few months someone sends me a bandwidth test that looks fine on paper and terrible in practice: 300 Mbps on a speed test, but a single large download crawls. The usual culprit is not bandwidth at all — it is the interaction between round-trip time, loss, and the congestion controller.
The arithmetic nobody enjoys
A classic loss-based controller treats a dropped packet as a congestion signal and
cuts its window hard. On a long path with even 2–3% loss, that means the window is
constantly collapsing right after it grows. Throughput ends up proportional to
MSS / (RTT * sqrt(p)). Double the RTT and you halve the ceiling;
double the loss and it gets worse again.
Bufferbloat makes it look like loss
A queue that is too deep converts loss into delay: nothing drops, but every packet sits in a buffer for hundreds of milliseconds, and the sender cannot tell the difference. An AQM such as fq_codel on the bottleneck link usually does more for perceived responsiveness than any amount of extra bandwidth.
What actually helped
- Measure one-way delay, not just ping. Jitter is the part users feel.
- Keep queues shallow and let the transport do its job.
- When the path is lossy, prefer a transport that is loss-tolerant rather than fighting the controller.
Filed under: transport, latency.