TechEarl

Speed Up a Linux Server With TCP BBR

Switch to TCP BBR congestion control with two sysctl lines and a reboot-safe config file. The throughput it buys you on lossy long-haul links, the fq qdisc it needs, the kernel version that ships it, and when CUBIC is still the right default.

Ishan Karunaratne⏱️ 9 min readUpdated
Share thisCopied
Enable TCP BBR congestion control on Linux with net.ipv4.tcp_congestion_control=bbr and net.core.default_qdisc=fq. The kernel version that ships BBR, the fq qdisc it needs, throughput gains on lossy links, and BBR vs CUBIC.

To turn on TCP BBR congestion control, set two kernel parameters and they take effect immediately:

bash
sudo sysctl -w net.core.default_qdisc=fq
sudo sysctl -w net.ipv4.tcp_congestion_control=bbr

That is the whole change. The first line sets the default queueing discipline to fq (fair queue), the second swaps the congestion-control algorithm from the kernel default (cubic) to bbr. BBR is a sending-side algorithm: only the machine doing the sending needs it, the client never has to know. No reboot, no library, no application change. To make it survive a reboot, drop those same two lines into a sysctl config file (covered below).

BBR (Bottleneck Bandwidth and Round-trip propagation time) is a congestion-control algorithm Google published in 2016 that models the path's bandwidth and RTT directly instead of treating packet loss as the congestion signal. On a long-haul link with even mild loss, that one difference can multiply throughput. It has been in the mainline Linux kernel since 4.9, so any reasonably current server already has it compiled in.

Check what you are running first

Before changing anything, see which algorithm is active and which ones the kernel can offer:

bash
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_available_congestion_control

Typical output on a stock server:

code
net.ipv4.tcp_congestion_control = cubic
net.ipv4.tcp_available_congestion_control = reno cubic

If bbr does not appear in the available list, the module is not loaded yet. Load it and confirm:

bash
sudo modprobe tcp_bbr
sysctl net.ipv4.tcp_available_congestion_control
code
net.ipv4.tcp_available_congestion_control = reno cubic bbr

On kernels 4.9 and newer BBR is almost always built as a module (CONFIG_TCP_CONG_BBR=m) and modprobe pulls it in. Check your kernel version with uname -r; if it is older than 4.9, BBR is not there and no sysctl will conjure it.

Make it permanent

sysctl -w is in-memory only and resets on reboot. To persist the change, write it to a drop-in file under /etc/sysctl.d/:

bash
echo 'net.core.default_qdisc = fq' | sudo tee /etc/sysctl.d/99-bbr.conf
echo 'net.ipv4.tcp_congestion_control = bbr' | sudo tee -a /etc/sysctl.d/99-bbr.conf
sudo sysctl --system

sysctl --system reloads every config under /etc/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.conf in order, so the new file applies without a reboot and also sticks across one. I prefer a dedicated 99-bbr.conf over editing /etc/sysctl.conf directly: it is one self-documenting file, easy to grep for, easy to remove if BBR turns out to be the wrong call for a given box.

Verify after the reload:

bash
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
code
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq

Why the fq qdisc matters

The net.core.default_qdisc = fq line is not optional decoration. BBR paces its packets: instead of dumping a window of data into the NIC at once, it spreads transmissions out at a computed rate to match its estimate of the bottleneck bandwidth. The pacing is what keeps BBR from overflowing buffers.

In the kernel BBR was originally designed to lean on the fq (fair queue) qdisc to do that pacing in the network stack. Newer kernels added pacing inside TCP itself, so BBR can technically run without fq, but that internal fallback uses one high-resolution timer per TCP socket, which costs more on a box holding many connections, where fq paces in one place. Google's own deployment guidance and the kernel notes still pair bbr with fq, and fq adds per-flow fairness on top, which is what you want on a busy server. Set both. Do not set default_qdisc=fq on a machine that does not also use a pacing-aware algorithm and expect a benefit; the two go together.

You can check the qdisc actually attached to an interface with tc:

bash
tc qdisc show dev eth0
code
qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p ...

What BBR actually buys you, and what it does not

BBR shines on a specific shape of problem: high bandwidth-delay product paths with non-congestive loss. That is the transcontinental or transoceanic link, the lossy mobile network, the path with a flaky middle hop. On those, loss-based algorithms like CUBIC read every dropped packet as "the network is full" and back off hard, collapsing throughput even though the loss came from line noise, not real congestion. BBR ignores loss as a signal and keeps sending at its measured rate, so it holds throughput where CUBIC would crater.

Where BBR does not help, or can hurt:

  • Clean, short-RTT links (a server talking to clients in the same datacenter or region). CUBIC is already near-optimal there; BBR gives you nothing measurable.
  • Fairness against CUBIC flows in a shared bottleneck. BBRv1, the version in mainline, can be aggressive toward concurrent CUBIC flows under some conditions, taking more than its fair share. Google addressed this in BBRv2 and the later BBRv3 work, but those live out of tree (see the google/bbr repo); mainline ships v1.
  • Deep-buffer paths where you want to fill the buffer. BBR deliberately keeps queues short. That is usually a feature (low latency), but it is a behavior change worth knowing.

Honest framing: BBR is a near-free win for an outbound-heavy public-facing server (web, API, media, file delivery) reaching a geographically spread audience. It is a no-op for an internal service on a clean LAN. Measure before and after with a real transfer over the real path, not a localhost loopback test that has no loss and no RTT to model.

BBR vs CUBIC at a glance

AspectCUBIC (kernel default)BBR
Congestion signalPacket lossMeasured bandwidth + RTT
Best onClean, low-RTT pathsHigh-BDP, lossy long-haul paths
Reaction to non-congestive lossBacks off hard, throughput dropsLargely ignores it, holds rate
Queue behaviorTends to fill buffers (bufferbloat)Keeps queues short, lower latency
Needs a pacing qdiscNoPairs with fq (recommended)
In mainline sinceLong-standing defaultKernel 4.9 (2016)
Fairness vs CUBICn/av1 can be aggressive; v2/v3 fix it, out of tree

Distro and kernel caveats

  • Kernel version is the gate. BBR needs 4.9+. Debian 9+, Ubuntu 18.04+, RHEL/Rocky/Alma 8+, and any current cloud image all clear that bar. A genuinely old box might not.
  • It is a module on most distros. If bbr is missing from tcp_available_congestion_control, run sudo modprobe tcp_bbr first. To load it automatically at boot independent of the sysctl drop-in, add a file under /etc/modules-load.d/, though setting tcp_congestion_control = bbr via sysctl normally triggers the load on its own.
  • Containers inherit the host. Congestion control and qdisc are host-kernel settings. Inside a container you cannot set a different algorithm than the host; change it on the host, and every container's TCP sends use it.
  • No client-side change needed. Because BBR is sender-side, enabling it on your server speeds up data you send to clients (downloads, responses). It does nothing for data clients send to you unless they also run BBR.

See also

FAQ

Sources

Authoritative references this article was fact-checked against.

TagsTCP BBRCongestion ControlLinuxsysctlNetworkingPerformanceSystem Administration

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts

Host a v3 .onion Hidden Service with Tor

End-to-end setup for a v3 .onion hidden service — torrc HiddenServiceDir and HiddenServicePort, key backup, permissions, onion-location header, single-onion mode, and the operational mistakes that get addresses leaked or lost.

How to Extend an AWS EBS Volume Without a Restart

Grow an EBS volume on a running EC2 instance in four steps. Modify the volume, wait for the optimizing state, expand the partition with growpart, then stretch the filesystem with resize2fs or xfs_growfs. No detach, no reboot.