Skip to content

TCP BBR

Every congestion-control algorithm covered so far — TCP Reno, TCP CUBIC — shares one foundational assumption: packet loss means the network is congested, and the way to find the network's real capacity is to keep growing the congestion window until loss happens, then back off. BBR, developed at Google and described in detail in the project's own public documentation, rejects that assumption directly. Its starting premise is that loss is often a poor proxy for congestion, and that a sender can do meaningfully better by measuring the network's actual behavior instead of inferring it from when packets get dropped.

Why "loss means congestion" isn't always true

Loss-based algorithms work by design on networks where a router's buffer filling up and overflowing is the primary reason packets go missing. That's a reasonable model for many paths, but it breaks down in at least two increasingly common situations:

  • Deep buffers ("bufferbloat"). Modern routers and switches often ship with buffers considerably larger than the ones TCP's original congestion-control model assumed. A large buffer means a sender can keep pushing more data, and the router keeps queuing it rather than dropping it, for far longer than a loss-based algorithm expects — the sender never sees the loss signal it's watching for, all while the actual latency of every packet is climbing steadily as the queue grows. The connection isn't losing data, so a loss-based algorithm keeps growing its window, but every packet is now taking meaningfully longer to arrive. Throughput might look fine on a graph; the experience for anything latency-sensitive sharing that same path is degrading the whole time.
  • Lossy links unrelated to congestion. TCP Reno already flagged this: a wireless link losing packets to radio interference looks, from a loss-based algorithm's perspective, identical to a wired link losing packets to a full router buffer. Both trigger the same reaction — shrink the window — even though only one of them reflects an actual capacity problem.

BBR's response to both cases is the same: stop treating loss as the primary signal, and instead directly estimate two physical properties of the path — how much data it can carry per second, and how long a round trip actually takes when nothing is queued up waiting.

The two measurements BBR is built around

BBR continuously estimates:

  • Bottleneck bandwidth (BtlBw) — the maximum rate the slowest link along the path can sustain, estimated from the delivery rate of ACKs actually observed over recent history.
  • Round-trip propagation time (RTprop) — the round-trip time the connection would have if there were no queuing delay anywhere along the path at all — as close to the physical, unavoidable latency of the route as the sender can estimate.

Both of these are real, physical properties of the network path, not artifacts of TCP's own behavior — the bottleneck bandwidth doesn't change because a sender is being cautious, and the propagation delay doesn't change because a router's queue is full. BBR's central idea is that a sender operating right at the point where it's using the full bottleneck bandwidth, without adding any of its own queuing delay on top of the path's true minimum round-trip time, is close to an optimal operating point — sending faster than that only adds queuing delay without adding real throughput, and sending slower leaves real capacity unused.

Why this changes what "probing" looks like

Because both BtlBw and RTprop can change over the life of a long connection — a shared link's available bandwidth shifts as other traffic comes and goes, and a mobile client's propagation delay changes as it moves between cell towers — BBR periodically probes for updated values of each, deliberately sending briefly faster than its current bandwidth estimate to see if more capacity is actually available, and briefly slower to get a clean, uncongested measurement of the true round-trip time without its own queued data distorting the reading. This is a fundamentally different rhythm from Reno's or CUBIC's sawtooth: rather than growing continuously until loss forces a reduction, a BBR sender is more directly regulating itself around its own live measurements of the path, only reacting to loss as one input among several rather than the defining signal.

Reading the algorithm on a live connection

On a Linux host with BBR compiled in and enabled, ss reports it exactly like it reports CUBIC:

sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic bbr
ss -tin dst 93.184.216.34
ESTAB 0 0 10.0.5.12:53100 93.184.216.34:443
      bbr wscale:7,8 rto:210 rtt:38.9/6.1 mss:1400 pacing_rate 118.5Mbps delivery_rate 96.2Mbps cwnd:64 bytes_sent:9481400 bytes_acked:9481400 segs_out:6774 segs_in:4102 send 220.1Mbps

Two fields here are specific to BBR-style operation and worth reading closely: pacing_rate — BBR actively paces segments out at a calculated rate rather than sending an entire window's worth in a burst, deliberately smoothing its own traffic rather than relying purely on ACK timing to space it out — and delivery_rate, the sender's live estimate of BtlBw, measured directly from observed ACK arrival rates rather than assumed from window growth. Neither field means anything under a purely loss-reactive algorithm like Reno or CUBIC, since those don't maintain an explicit bandwidth estimate as a first-class part of their operation at all.

Where BBR earns its keep, and where it doesn't automatically win

BBR was developed specifically with the deep-buffer and lossy-link problems above in mind, and it's most clearly advantageous on paths where those problems are real: long-haul links with large intermediate buffers, mobile and wireless connections where non-congestive loss is common, and generally any path where a loss-based algorithm would either under-use available bandwidth (deep buffers hiding real capacity) or over-react to noise (non-congestive loss).

It is not automatically the right choice everywhere, and it's worth resisting the instinct to treat a newer algorithm as a strictly better one in every context. On a short, low-latency, well-provisioned data-center network, the differences between CUBIC and BBR are far less consequential, since neither of BBR's target problems — deep-buffer-induced queuing delay, or non-congestive loss — is likely to be present in the first place. And because BBR doesn't treat loss as its primary throttle, a BBR sender competing on a shared, congested bottleneck against loss-based senders can, depending on the specific scenario and BBR version, claim a disproportionate share of the link — a real fairness consideration that's been an active area of refinement across BBR's own version history (BBRv1 through the current work), not a settled, closed question.

Practical scenario: a video conferencing backend rolling out BBR to cut latency, not to raise throughput

A company running a real-time video conferencing backend notices that call quality on long-distance connections — remote workers connecting across continents, in particular — degrades under load in a specific way: audio and video occasionally lag behind in a way that gets worse the longer a call runs, even though iperf3 throughput tests on the same paths look consistently fine. Packet loss on these calls, measured separately, is low and not the obvious culprit.

This combination — fine raw throughput, but worsening real-time latency the longer a connection is active, on paths that likely traverse ISP or backbone equipment with generous buffer sizes — is the bufferbloat pattern this article opened with: a CUBIC-based sender, seeing no loss, keeps finding more window to fill, and the shared router buffer along the path absorbs the extra data by queuing it rather than dropping it, adding real, growing latency the whole time. Throughput-focused testing (iperf3) doesn't reveal this, because raw throughput is exactly what a large buffer preserves — it's latency, not throughput, that degrades as the queue grows.

Validate before changing a shared backend's default

Switching a production video backend's congestion control is a real behavioral change affecting every active call, not a cosmetic tuning knob. Test the change against a realistic long-distance path in a staging environment first, measure both throughput and round-trip latency under sustained load before and after, and roll it out gradually rather than as a single global flip — a change intended to fix one class of connection (long-distance, high-latency) should be verified not to regress the common case (short, low-latency, already-healthy connections) before it reaches every user.

Rolling BBR out on the media servers handling these specific long-distance connections, and confirming the fix with ss -tin's pacing_rate and delivery_rate fields alongside a direct round-trip latency measurement under load, is the concrete way to confirm the diagnosis rather than assume the algorithm change alone explains an improvement.

Practice exercises

  1. Explain, in your own words, why a loss-based algorithm like CUBIC can keep growing its congestion window indefinitely on a path with a very large router buffer, even though the actual experience of using that path (measured in latency, not throughput) is getting steadily worse the whole time.
  2. A team proposes switching every server in a low-latency, same-datacenter microservice mesh from CUBIC to BBR, expecting a meaningful performance win. Using this article's discussion of where BBR's advantages actually come from, evaluate whether that expectation is well-founded for this specific environment.

This closes out the family of algorithms that decide how much data a TCP sender is willing to have in flight. Everything from here on is a different constraint entirely — not how much data can be outstanding, but how large any single segment of that data is allowed to be in the first place, which starts with MTU: the hard size limit imposed by the physical network underneath TCP.

Sources