BBRv3 is in the kernel. CUBIC is still the default. Here is when to care.
Published by RodHat

TCP congestion control is one of those things where the correct answer has been wrong for thirty years and everyone has just quietly accepted it. CUBIC backs off when it sees packet loss. Packet loss is not the signal you want. On modern high-bandwidth, high-latency paths, packet loss shows up because buffers filled, and buffers filled because the sender kept increasing its window until something dropped. You got the drop because you were already too late. The algorithm is reactive to symptoms rather than to the actual bottleneck condition.
BBR (Bottleneck Bandwidth and Round-trip time) was Google’s fix. Instead of watching for drops, BBR models two quantities explicitly: the bottleneck link’s delivery rate and the minimum round-trip time. It probes the bandwidth ceiling with brief bursts, then paces the send rate against what it measured. The result is a sender that does not need a queue to fill and drop before it knows it has hit a limit.
BBRv1 merged into Linux 4.9 in late 2016. It shipped in GCP on the same day Google announced it. Within six months every hyperscaler had evaluated it. A lot of them flipped the default.
Then the papers came out showing that BBRv1 was a bad neighbor on shared paths.
What went wrong with BBRv1
On a path shared between a BBRv1 flow and several CUBIC flows, BBRv1 grabbed a disproportionate share of bandwidth. Not because of malice in the design, but because the probe-bandwidth phase kept pushing the window up regardless of what CUBIC flows were doing to respond to the same link. CUBIC backs off at loss; BBRv1 does not use loss as a signal, so it does not back off in the same conditions. On a link with controlled traffic between cooperating machines you control, this does not matter. On an internet path shared with arbitrary other flows, it does.
The academic measurement was uncomfortable reading. The standard line from the Google team was that the fairness issue was known and being worked on. That was 2017.
BBRv2 was a research vehicle, not a production release. It addressed some of the fairness problems but introduced instability under certain loss patterns that made it worse than BBRv1 in the failure cases that matter most. It landed in a handful of Google-internal environments and never made the kernel tree.
BBRv3 is the production version. It was posted as a patch series to the netdev mailing list in mid-2023, went through several revisions, and merged into the mainline kernel. The fairness fix works by making BBRv3 pay attention to ECN and adjusting the pacing rate when congestion signals come in from the network, even without drop. On shared paths it converges to a fair split with CUBIC flows. The throughput advantage BBRv1 showed on long-fat-pipe paths is preserved. The antisocial behavior is gone.
What the sysctl does
# check what your kernel supports
sysctl net.ipv4.tcp_available_congestion_control
# switch to bbr for the current session
sysctl -w net.ipv4.tcp_congestion_control=bbr
# persist across reboots
echo 'net.ipv4.tcp_congestion_control = bbr' >> /etc/sysctl.d/99-tcp-bbr.conf
That is the whole change for a single-algorithm default. If you want per-socket control
(different workloads on the same host, or testing one algorithm against another), you do it
through SO_CONGESTION_CONTROL on the socket before connect(2) or accept(2). The
kernel has per-socket override support; your application just needs to call it.
You also want FQ (Fair Queue) pacing alongside BBR:
# check the current qdisc on the outbound interface
tc qdisc show dev eth0
# set fq pacing if not already there
tc qdisc replace dev eth0 root fq
BBR relies on pacing at the qdisc layer to spread packets across the available window rather than sending in bursts. Without FQ, BBRv3 still works but you leave performance on the table. FQ pacing is cheap and has no downside on single-tenant hosts.
When to actually switch
BBRv3 is the correct default if you are running:
A long-distance or intercontinental connection. BBR was designed for high-bandwidth-delay product paths. CUBIC is fine on a LAN. On a path from your origin to users in Southeast Asia, BBR’s explicit bandwidth modeling does real work that CUBIC’s loss-reaction loop cannot replicate.
A server with high-throughput TCP sessions where buffer bloat is the enemy. Bulk data transfer, video streaming origins, large file downloads. BBR keeps RTT low while maximizing throughput; CUBIC lets buffers fill before backing off.
A connection going through lossy last-mile links. CUBIC interprets every drop as congestion and halves its window. If drops are coming from RF interference on a wireless backhaul, not from queue overflow, CUBIC’s reaction is wrong. BBR does not overreact to loss signals.
When to leave CUBIC alone:
High-frequency trading or any workload where you send a small burst and then wait for a response. BBR’s bandwidth probing introduces periodic deliberate bursts that measure the ceiling. For tiny short-lived transactions the overhead is noise and the benefit is zero.
Datacenter east-west traffic between machines with 10 GbE or better on a well-managed fabric. Packet loss is near zero, RTT is under a millisecond, and CUBIC converges fast enough that you will not measure a difference. The measurement headroom is gone before BBR has time to probe anything useful.
Any path where the bottleneck is not bandwidth or RTT but something structural, like middleboxes doing deep packet inspection, or rate limiting that is not expressed through queue depth. BBR models the network; it does not model bureaucratic chokepoints.
The kernel version requirement
BBRv3 went into the mainline tree. tcp_available_congestion_control will tell you
whether bbr is available on your kernel. If it is not in the list, you need a newer
kernel or a kernel built with CONFIG_TCP_CONG_BBR=y.
Linux distributions are slow to change TCP congestion control defaults even after the kernel supports an alternative, because the change is hard to roll back if it causes a regression in one customer’s workload. You will not get BBRv3 as the system default from a distro update. You have to set it yourself.
Check whether your current setup is loss-signaling in anger:
# connections with retransmits > 0, sorted by retransmit count
ss -i state established | awk '/retrans:/{print $0}' | grep -v 'retrans:0' | head -20
If that list is long and the connections are long-lived, you have a loss-driven congestion story. BBRv3 is the appropriate correction.
The ss -i TCP internals reference covers reading
the full tcp_info output including cwnd, rtt, and retrans in detail. The
TCP-AO migration post from last week is
adjacent context if you are also thinking about the authentication layer on long-running
sessions.
If your kernel has bbr in tcp_available_congestion_control and your workload is
long-lived connections over non-local paths, there is no good reason to stay on CUBIC.
The fairness issue that killed BBRv1 in mixed environments is gone. The perf advantage
on high-latency paths is real. The sysctl is one line.
Sources
- TCP BBR congestion control merge commit, Linux 4.9 (Linus Torvalds / kernel.org)
- BBR Congestion Control, Google Cloud Networking blog (Google)