BBR Congestion Control

BBR — “Bottleneck Bandwidth and Round-trip propagation time” — is a model-based congestion-control algorithm developed at Google and first described publicly by Cardwell, Cheng, Gunn, Yeganeh, and Jacobson in ACM Queue in 2016 (Cardwell et al. 2016; republished in Communications of the ACM, 2017). Unlike the loss-based algorithms that preceded it — Reno and CUBIC — BBR does not treat a dropped packet as the definition of congestion. Instead it continuously estimates two physical properties of the path: the bottleneck bandwidth (BtlBw), the fastest rate the slowest link can deliver, and the round-trip propagation delay (RTprop), the path’s latency with no queueing. It then sets its sending rate to pace packets at roughly BtlBw and keeps the data in flight near the bandwidth-delay product (BtlBw × RTprop) — just enough to keep the bottleneck fully utilised without building a standing queue. The payoff is high throughput and low latency even on paths where loss-based control either starves throughput (shallow buffers with random loss) or inflates latency (deep buffers, i.e. bufferbloat).

Uncertain

Version status (as of 2026-07): Only BBRv1 was ever formally published — the 2016 ACM Queue / 2017 CACM article. BBRv2 and BBRv3 are IETF Internet-Drafts, not RFCs. BBRv3 is specified in draft-ietf-ccwg-bbr, which was at draft-05 in the IETF Congestion Control Working Group (CCWG), last revised 2026-03-02, expiring 2026-09-03 (datatracker; draft-05 text). Drafts self-describe as experimental and are revised frequently — do not cite BBRv2/v3 as a “standard.” The numeric BBRv1 gain constants below (STARTUP gain 2/ln 2, the eight-phase PROBE_BW cycle, PROBE_RTT to 4 packets) are corroborated from the IETF drafts and a peer-reviewed analysis (PMC 10181671) because the ACM Queue full text returned HTTP 403 and the Stanford PDF mirror was not machine-readable during this research; the values are widely agreed but were not read verbatim from the original paper. #uncertain

This note is the protocol / algorithm view of BBR — the model, the estimators, the state machine, the design trade-offs, and the fairness controversy. The Linux kernel implementation — the tcp_congestion_ops vtable, the cong_control hook BBR uses to set both cwnd and the pacing rate, module autoloading, and observing it with ss -i — lives in the sibling note TCP Congestion Control; this note cross-links it rather than duplicating it. The foundational sawtooth BBR breaks away from is in Congestion Control Fundamentals and AIMD, and the loss-based default it competes with is in CUBIC Congestion Control.

Mental Model — Fill the Pipe, Not the Buffer

The clearest way to picture BBR is the physical “pipe” a connection flows through. A path is a chain of links; the bottleneck is the single slowest one, and it alone sets the maximum delivery rate BtlBw. In front of the bottleneck sits a buffer (a router queue). A sender can put bytes into the network faster than BtlBw for a while, but those excess bytes do not travel faster — they pile up in the bottleneck buffer, adding queueing delay to every packet behind them, and eventually overflow it, causing loss.

The key quantity is the bandwidth-delay product (BDP) — BtlBw multiplied by RTprop. This is the amount of data that exactly fills the pipe end-to-end when there is no queue: enough in flight to keep the bottleneck busy every instant, but not one byte in a buffer. This is precisely Leonard Kleinrock’s optimal operating point (1979), where throughput is maximised and delay is minimised simultaneously (Cardwell et al. 2016). BBR’s entire objective is to drive the connection to that point and hold it there.

flowchart LR
  subgraph inflight["Amount of data in flight →"]
    A["app-limited<br/>(inflight &lt; BDP)<br/>pipe not full,<br/>bottleneck idle sometimes"]
    B["OPTIMAL: inflight = BDP<br/>pipe full, buffer empty<br/>max throughput + min RTT<br/>← BBR lives here"]
    C["buffer-filling<br/>(BDP &lt; inflight &lt; BDP+buf)<br/>full throughput but<br/>growing queue delay"]
    D["loss<br/>(inflight &gt; BDP + buffer)<br/>buffer overflows<br/>← loss-based CC lives here"]
  end
  A --> B --> C --> D

Where each family of congestion control operates on the “inflight data” axis. What it shows: as a sender increases the bytes in flight, throughput rises until inflight reaches the BDP, after which throughput is flat (the bottleneck is already saturated) and the only thing that grows is queueing delay — until the buffer overflows and packets drop. The insight to take: loss-based algorithms like CUBIC keep pushing until they cause loss, so they operate at the right-hand edge (inflight ≈ BDP + buffer), which is exactly where latency is worst — this is bufferbloat. BBR deliberately aims for the left edge of the flat region (inflight ≈ BDP), getting the same throughput with a near-empty buffer. Loss and BBR’s target are separated by the entire width of the buffer, which on modern deep-buffered links is enormous.

Why Loss Is a Bad Congestion Signal

Loss-based control was a brilliant fit for 1980s networks, where buffers were shallow and roughly BDP-sized, so a full buffer (and thus loss) really did coincide with the optimal point. Two modern realities broke that coincidence (draft-cardwell-ccwg-bbr-00):

  • Deep buffers (bufferbloat). Memory got cheap, so router and switch buffers grew to hundreds of milliseconds’ worth of data. A loss-based sender fills that whole buffer before it sees a drop, so it operates far to the right of optimal — the buffer-filling region — adding gratuitous latency to every flow sharing the link. The throughput is fine; the latency is ruined.
  • Shallow buffers with random loss. On the other extreme, links with small buffers (or lossy physical media like Wi-Fi and cellular) drop packets for reasons unrelated to congestion, even when the link is barely utilised. A loss-based algorithm reads each such drop as “the network is congested,” slashes its window, and crawls back up. On a high-BDP path a single sporadic loss can cap throughput at a small fraction of capacity — the paper cites the Mathis model showing that at a loss rate of 10⁻⁶ on a long fat pipe, loss-based control leaves throughput orders of magnitude below the link rate.

BBR sidesteps both by ignoring loss as the primary signal and measuring the two path parameters directly. This is the meaning of “congestion-based” (BBR) versus “loss-based” (Reno/CUBIC): BBR reacts to a model of the path, not to a symptom that may or may not indicate congestion.

The Two Parameters and Their Estimators

BBR characterises the path with exactly two numbers, and the trick of the whole design is how it measures them without a controlled experiment.

RTprop (round-trip propagation delay) is the path latency with no queue. Any queueing only adds to the measured RTT, so the true propagation delay is the minimum RTT ever seen. BBR therefore estimates it as a windowed minimum of round-trip time samples over a recent window — 10 seconds in BBRv1 (MinRTTFilterLen = 10 s, draft-cardwell-iccrg-bbr-02). If the minimum hasn’t refreshed in that window, BBR suspects its estimate is stale (a persistent queue may be hiding the true floor) and takes deliberate action — see PROBE_RTT below.

BtlBw (bottleneck bandwidth) is the maximum delivery rate the path can sustain. BBR measures delivery rate directly: for each ACK it computes delivery_rate = (data delivered) / (elapsed time) over the interval the ACK covers, a quantity the sender can compute from its own records without receiver cooperation. Because transient effects (ACK compression, aggregation) can only make a single sample look slower or faster than reality, the sustainable bottleneck rate is the maximum of recent samples: BBR estimates BtlBw as a windowed maximum of delivery-rate samples over the last several round trips (~10 RTTs in BBRv1).

The subtlety the paper stresses is an uncertainty principle: you cannot measure BtlBw and RTprop at the same time. To measure the true (unqueued) RTprop you must send slowly enough that there is no queue — but then you are not saturating the link, so you cannot see the true BtlBw. To measure BtlBw you must fill the pipe (and briefly build a queue) — but that queue inflates RTT, hiding RTprop. So BBR measures them alternately: most of the time it probes bandwidth (accepting a tiny transient queue), and periodically it deliberately empties the pipe to re-measure propagation delay. The state machine below is essentially a schedule for interleaving these two measurements.

The Control Law — Pacing, Not a Window Alone

Classic TCP is ACK-clocked: it releases a new segment whenever an ACK frees a slot in cwnd, so its sending is as bursty as the ACK arrivals. BBR instead paces: it spaces packets out in time so that its average send rate equals a target, computed as

pacing_rate = pacing_gain × BtlBw
cwnd        = cwnd_gain  × BtlBw × RTprop   (= cwnd_gain × BDP)

(Cardwell et al. 2016; the identical control law appears in the Linux tcp_bbr module header, per TCP Congestion Control.) The pacing rate is the primary control — it decides how fast bytes enter the network. The congestion window is a secondary safety cap on how many bytes are outstanding, set to a small multiple of the BDP (cwnd_gain = 2 in BBRv1) so that ACK jitter or a brief measurement glitch cannot let inflight run away. The two gainspacing_gain and cwnd_gain — are the knobs the state machine turns: a pacing_gain > 1 sends faster than BtlBw to probe for more bandwidth (and briefly build a queue); a pacing_gain < 1 sends slower to drain any queue that probing created; pacing_gain = 1 cruises at the estimated bottleneck rate.

Because BBR paces, it depends on the sender being able to time packets out precisely; on Linux this means pairing it with a pacing-capable queueing discipline (fq) or the kernel’s internal pacing — an implementation detail owned by TCP Congestion Control.

The State Machine (BBRv1)

BBRv1 is a four-state machine that schedules bandwidth-probing and RTprop-probing over time.

stateDiagram-v2
  [*] --> STARTUP
  STARTUP --> DRAIN: pipe full<br/>(bw plateaus 3 rounds)
  DRAIN --> PROBE_BW: queue drained<br/>(inflight ≈ BDP)
  PROBE_BW --> PROBE_RTT: RTprop stale<br/>(no new min in 10 s)
  PROBE_RTT --> PROBE_BW: RTprop re-measured<br/>(held ≥ 200 ms)

  note right of STARTUP
    gain = 2/ln2 ≈ 2.89
    exponential ramp,
    doubles rate per RTT
  end note
  note right of PROBE_BW
    steady state
    8-phase pacing_gain cycle:
    [1.25, 0.75, 1,1,1,1,1,1]
    one RTprop per phase
  end note
  note right of PROBE_RTT
    cwnd → 4 packets
    hold ≥ 200 ms
    to see true RTprop
  end note

The BBRv1 state machine. What it shows: BBR ramps up fast (STARTUP), sheds the queue that ramp created (DRAIN), then spends almost all its time in PROBE_BW cycling a gain schedule that gently probes for more bandwidth and drains the result, dipping occasionally into PROBE_RTT to re-measure the propagation floor. The insight to take: the machine exists to solve the “can’t measure both at once” problem — PROBE_BW keeps BtlBw fresh, PROBE_RTT keeps RTprop fresh, and the design spends the overwhelming majority of time in the former because bandwidth changes more often than propagation delay.

STARTUP is BBR’s analogue of slow start. Both pacing_gain and cwnd_gain are set to 2/ln 2 ≈ 2.89, the smallest gain that doubles the sending rate every round trip (PMC 10181671). BBR ramps up exponentially, and because it is measuring delivery rate the whole time, it detects the pipe is full when the measured BtlBw stops rising for three consecutive rounds (a plateau) — an estimate the paper notes overshoots the BDP by roughly a factor of two by the time the plateau is confirmed.

DRAIN cleans up that overshoot. It flips the pacing gain to the reciprocal, pacing_gain = ln 2 / 2 ≈ 0.35, sending below BtlBw so the queue STARTUP built empties out, until inflight falls back to one BDP. Then it enters steady state.

PROBE_BW is where a long-lived BBR flow spends ~98 % of its life. It cycles the pacing gain through an eight-phase sequence, [1.25, 0.75, 1, 1, 1, 1, 1, 1], spending about one RTprop in each phase (PMC 10181671). The 1.25 phase sends 25 % faster than the estimated BtlBw to probe whether more bandwidth has become available (if it has, the delivery-rate samples rise and BtlBw is revised up). The 0.75 phase then sends 25 % slower to drain the small queue the probe created and let any competing flow reclaim its share. The six 1.0 phases simply cruise at the estimated rate. This gentle, self-correcting cycle is how BBR tracks a changing bottleneck without ever needing loss as a trigger.

PROBE_RTT handles the stale-RTprop case. If no new RTT minimum has appeared for 10 seconds — which usually means a persistent queue (perhaps built by BBR itself or by competing traffic) is hiding the true propagation delay — BBR drops its congestion window to a floor of 4 packets (BBRMinPipeCwnd = 4) for at least 200 ms (ProbeRTTDuration). Draining almost all inflight lets the bottleneck queue empty, so the next few RTT samples reveal the genuine, unqueued RTprop; BBR records it and returns to PROBE_BW. This brief, sharp reduction is the price of keeping the RTprop estimate honest, and it is deliberately short and infrequent to minimise its throughput cost.

The Fairness Controversy — BBRv1’s Real Weaknesses

BBRv1’s design has two well-documented fairness pathologies, and they are the reason BBRv2/v3 exist.

RTT unfairness (intra-protocol). Because a flow’s target inflight is cwnd_gain × BtlBw × RTprop, a flow with a longer RTprop is entitled to more inflight data. When a long-RTT and a short-RTT BBR flow share a bottleneck, the long-RTT flow keeps more data queued and captures a disproportionate share of the link. A peer-reviewed measurement found that on a shared bottleneck a 50 ms-RTT BBR flow obtained roughly 4.7× the throughput of a 10 ms-RTT BBR flow — the opposite of the RTT-fairness direction and far more skewed than CUBIC’s mild bias (PMC 10181671).

Aggressiveness toward loss-based flows (inter-protocol). BBRv1 ignores loss almost entirely and caps its inflight at ~2×BDP (cwnd_gain = 2) regardless of what else is on the link. On a shared deep-buffered bottleneck, that fixed 2×BDP of BBR data occupies a large, constant chunk of the buffer, and coexisting CUBIC/Reno flows — which do back off on the loss BBR induces — get squeezed into whatever is left, sometimes starved. Conversely, on shallow buffers BBRv1 can drive high loss rates because it keeps probing past the buffer’s capacity (draft-cardwell-ccwg-bbr-00). This “BBR is unfair to CUBIC” result was the central criticism of BBRv1 after deployment.

What BBRv2/v3 change (still drafts). BBRv3 keeps loss as a secondary bound on the model rather than a primary trigger. It introduces a maximum tolerated loss rate, BBR.LossThresh = 2 % per round while probing, and adds an explicit response to ECN (Explicit Congestion Notification) marks — an experimental version that treats a Congestion-Experienced mark as a congestion signal without pinning down a single response curve (draft-ietf-ccwg-bbr-05). It replaces the single BtlBw/RTprop pair with short-term and long-term bounds on both bandwidth (bw_hi/bw_lo) and inflight (inflight_hi/inflight_lo), so it can back off quickly when a loss or ECN signal implies the model is too optimistic, then re-probe on a slower timescale to recover capacity. The PROBE_BW cycle is also restructured into named sub-phases — DOWN (gain 0.9), CRUISE (1.0), REFILL (1.0), UP (1.25) — rather than BBRv1’s fixed eight-slot array (draft-cardwell-iccrg-bbr-02). Explicit fairness machinery bounds how often and how aggressively BBR probes so that a coexisting Reno/CUBIC flow can reach a comparable rate. Whether BBRv2/v3 fully solve the fairness problems is still contested in the literature; the drafts themselves are labelled experimental.

Uncertain

Verify: the exact BBRv3 PROBE_BW sub-phase gains (DOWN 0.9 / CRUISE 1.0 / REFILL 1.0 / UP 1.25) and the StartupPacingGain constant. Reason: draft-cardwell-iccrg-bbr-02 describes BBRv2 and gives these sub-phase gains, while draft-ietf-ccwg-bbr-05 (BBRv3) restates them and defines StartupPacingGain as “4·ln 2 ≈ 2.77” — numerically different from BBRv1’s canonical 2/ln 2 ≈ 2.89 startup gain, and the drafts evolve between revisions. To resolve: read the exact constants block of the targeted draft-ietf-ccwg-bbr revision, since these values change draft-to-draft. #uncertain

Deployment — What Google Measured

BBR was not a paper exercise; Google deployed it at scale before publishing. On its B4 software-defined WAN — the private backbone connecting its data centres — Google began switching production traffic from CUBIC to BBR in 2015, saw no regressions, and moved all B4 TCP traffic to BBR by 2016, with measured throughput 2× to 25× that of CUBIC on those long, fat, low-loss paths (Cardwell et al. 2016; morning-paper summary). On public-facing Google and YouTube servers, BBR reduced median RTT by ~53 % on average globally, and by more than 80 % in developing regions — the latency win from not filling buffers. A striking anecdote from the paper: on YouTube, ~75 % of BBR connections were actually being capped by the kernel’s receive buffer (deliberately set to 8 MB to stop CUBIC from flooding the network); after manually raising the receive buffer on one US–Europe path, BBR immediately reached 2 Gbps where CUBIC was stuck at 15 Mbps — a 133× gap that matched the Mathis-model prediction for that loss rate. The lesson: on high-BDP paths, loss-based control can leave almost all of the pipe unused, and the bottleneck for BBR is often something other than congestion control.

Alternatives and When to Choose BBR

  • CUBIC (the loss-based default). Fair to other CUBIC flows, well-understood, safe on the general Internet. Choose it (i.e. don’t switch) unless you have a measured reason. BBR can be unfair to CUBIC on a shared bottleneck, so mixing them on the same link deserves care.
  • BBR — choose for high-throughput, long-fat, or lossy paths. CDN edges, video delivery, transcontinental bulk transfer, and lossy last-mile links (Wi-Fi, cellular) are where loss-based backoff leaves bandwidth on the table or deep buffers ruin latency. BBR’s model-based approach shines exactly there. Pair it with a pacing qdisc.
  • Reno / NewReno. The textbook AIMD baseline (Congestion Control Fundamentals and AIMD); a fallback, not a performance choice.
  • DCTCP and other ECN-based schemes. Inside a single data centre where every hop marks ECN, an ECN-reactive algorithm gives very low latency — but requires end-to-end ECN and is unsuitable for the open Internet. BBRv3’s optional ECN response is a partial move in this direction.

The honest summary: BBR is a genuine advance in not conflating loss with congestion, and a clear win on the paths it was designed for, but its fairness behaviour (especially BBRv1’s) means “switch everything to BBR” is not free — benchmark against your real traffic mix before deploying fleet-wide.

See Also