Optimize HTTP/3 performance on high-latency links with this expert QUIC tuning guide. Essential parameters for BBR, flow control, and window sizing. Read now.
Why High-Latency Links Change the Tuning Math
On a link with high round-trip time, throughput is governed less by raw bandwidth than by how much data a sender can keep in flight before it must wait for an acknowledgment. If the amount of unacknowledged data allowed is smaller than the bandwidth-delay product of the path, the connection stalls waiting for ACKs to come back, and the extra capacity of the link goes unused. QUIC runs over UDP and manages its own congestion control, loss recovery, and flow control in user space, which means these limits are yours to set rather than something the kernel decides for you.
The practical goal of tuning is to make the allowed in-flight data large enough to fill the pipe on your worst-case path, while keeping enough backpressure that a single connection cannot exhaust memory or starve others. Everything below is a lever toward that balance.
Congestion Control: Choosing and Configuring BBR
The congestion controller decides how fast the sender may transmit and how it reacts to loss. Loss-based controllers treat a dropped packet as a signal to back off, which penalizes long, lossy paths where drops are not always a sign of congestion. BBR instead models the path's bandwidth and round-trip time directly and paces packets to match the estimated capacity, so it tends to recover throughput better on high-latency links with sporadic loss.
When you enable BBR in a QUIC stack, verify that packet pacing is on, since BBR depends on spreading packets across the round trip rather than sending them in bursts. Watch for a controller that clamps its window too aggressively during startup; on a long path it can take several round trips to probe up to the true bandwidth, and cutting that probe short leaves throughput on the table.
Flow Control and Window Sizing
QUIC applies flow control at two levels: per stream and for the connection as a whole. Each has an independent limit on how much data the peer may send before receiving a window update. On a high-latency path these limits must be sized against the bandwidth-delay product, because a window that empties before the next update arrives forces the sender to pause even when congestion control would allow more.
Size the connection window larger than any single stream window so that one stream cannot consume the entire connection budget, but keep both generous enough to cover a full round trip of data. The main parameters to review:
- Initial max stream data — the starting per-stream receive limit, applied before the peer sends any updates.
- Initial max connection data — the aggregate receive limit across all streams on the connection.
- Max concurrent streams — how many streams may be open at once, which caps parallelism and total buffering.
- Receive buffer sizing — the memory backing those windows, which must be large enough to hold the advertised limits.
Validating Your Changes
Tuning without measurement tends to trade one bottleneck for another. After each change, test against a path that reflects your real latency and loss rather than a local loopback, which hides the very effects you are tuning for. Compare achieved throughput to the theoretical ceiling implied by your window sizes and the path's round-trip time; if you fall short of that ceiling, the limit is usually a window that is too small or a controller still probing. If you meet it but memory climbs, your windows are larger than the path needs and can be trimmed.
Change one parameter at a time and keep a record of what each adjustment did. The interactions between congestion control and flow control are easy to misread when several values move together, and a clear before-and-after per change is what turns guesswork into a repeatable configuration.