TCP BBR vs CUBIC on a VPS: A Reproducible Benchmark Methodology

Switching a VPS to BBR is a two-line change that people either swear by or claim does nothing. The truth is measured: BBR helps most on links with loss and latency, and changes almost nothing on a clean low-latency LAN. This is a methodology to decide for your VPS instead of copying a blog post.

Prerequisites and the congestion control modules

sysctl net.ipv4.tcp_available_congestion_control
# default on modern kernels includes bbr
lsmod | grep tcp_bbr || modprobe tcp_bbr
sysctl -w net.core.default_qdisc=fq
sysctl -w net.ipv4.tcp_congestion_control=bbr
# persist in /etc/sysctl.d/99-bbr.conf

fq (fair queueing) is the pacing qdisc BBR depends on. Without it BBR still works but loses its pacing advantage. Verify the effective setting after applying:

sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
# net.ipv4.tcp_congestion_control = bbr
# net.core.default_qdisc = fq

Design the test before you touch anything

  • Fix variables: same client, same server, same time of day, same payload size.
  • Test both directions separately (upload and download).
  • Measure at least three metrics: throughput, RTT, and retransmits.
  • Repeat each run 5x and take the median — single runs are noise.

For a fair comparison, benchmark the same link twice: once with CUBIC, once with BBR, changing only tcp_congestion_control.

# server side: keep iperf3 listening
 iperf3 -s

# client, CUBIC run (revert first)
sysctl -w net.ipv4.tcp_congestion_control=cubic
iperf3 -c SERVER_IP -t 30 -R --json > cubic_reverse.json

# client, BBR run
sysctl -w net.ipv4.tcp_congestion_control=bbr
iperf3 -c SERVER_IP -t 30 -R --json > bbr_reverse.json

Reading the results, not just the Mbps number

MetricWhere it shows upWhat it tells you
Throughputiperf3 sum bits/secRaw goodput — but a clean link shows little difference
RTT–json end.sum_rttBBR’s model reacts to RTT; sanity-check path latency
RetransmitsTCPRetransmits / ss -tiLoss on the path is where BBR usually wins
Delivery ratess -ti ‘bw =’Per-socket estimate of achieved rate

If your link has effectively zero loss, expect CUBIC and BBR within a few percent — the change is not worth the risk of an untested qdisc. If you see retransmits on the path (common on long-haul or congested uplinks), BBR typically recovers more throughput because it does not interpret loss as a hard capacity signal.

Confirm at the socket level

# watch per-connection congestion state and delivery rate
ss -ti dst SERVER_IP
# look for: bbr  cwnd:..  rtt:..  bw:..

A BBR socket reports bbr as the algorithm and exposes bw (delivery rate). If you still see cubic, your sysctl was not applied to new sockets — existing connections keep their original algorithm.

Caveats specific to VPS hosts

  • Bufferbloat elsewhere on the path can mask BBR’s benefit; fix the queue first if local (see fq_codel/CAKE).
  • BBR is sender-side; you control only your VPS, not the remote peer.
  • BBR competes differently with loss-based flows sharing the same bottleneck — do not assume you win every time.
  • CPU-sensitive: on a single-vCPU box, high-rate BBR can cost measurable CPU versus CUBIC.

Running the benchmark across real latency

iperf3 on a LAN tells you almost nothing about a WAN path. To make the test meaningful, inject latency and loss so the difference between algorithms has room to show. tc netem on a test interface lets you reproduce a realistic path deterministically:

# on a test host, add 100ms RTT and 1% loss outbound
sudo tc qdisc add dev eth0 root netem delay 50ms loss 1%

# now repeat the CUBIC and BBR iperf3 runs
sudo tc qdisc del dev eth0 root   # remove when done

With 50ms one-way delay and 1% loss, CUBIC typically backs off hard on each loss event while BBR holds throughput closer to the path’s capacity. That is the scenario where the change pays for itself — and the scenario a clean LAN test would never reveal.

Turning results into a decision

Your path looks likeExpected BBR gainRecommendation
Clean LAN / loopback~0%Leave CUBIC; no benefit, added complexity
Low RTT, no loss<5%Not worth changing on a production box
High RTT + lossOften 20–200%Enable BBR + fq and re-measure
Shared bottleneck, many flowsVariableTest under realistic contention before committing

Roll it out the way you would any other kernel tuning: on one host, measured before and after, with a rollback command ready. To revert, set net.ipv4.tcp_congestion_control=cubic and remove the default_qdisc line, then confirm new sockets report the old algorithm.

Change one dial at a time, measure the same path before and after, and keep a note of the kernel version — BBR’s behaviour has evolved. For a workload where every millisecond of TTFB counts, combine this with latency reduction work at the edge rather than tuning the transport in isolation — the TTFB and edge-caching approach stacks well with a tuned transport.

On a modest cloud instance, this is one of the few kernel changes with a large, repeatable upside on real-world traffic, provided you validate it on a path that actually has loss. Skip the validation and you are just hoping.

Leave a Reply