TCP CUBIC
TCP Reno closed on the specific case where its linear, one-segment-per-round-trip growth becomes a real liability: long, high-bandwidth, high-latency paths, where climbing back to a large congestion window after any loss event takes an uncomfortably long time relative to how briefly the loss itself actually mattered. CUBIC is the algorithm that fixes exactly that case, and it's been the default congestion-control algorithm in the Linux kernel for a long time — if a Linux server hasn't had its congestion control explicitly changed, it's very likely running CUBIC right now.
The problem stated precisely: additive increase doesn't scale with distance
Reno's congestion avoidance adds one maximum segment size to the congestion window per round trip, full stop, regardless of how large that window already is or how much round-trip time separates the two ends. That's a flat, constant growth rate. On a short, low-latency connection — two servers in the same data center — round trips happen so often that even a small per-round-trip increase adds up quickly in real time. On a long, high-latency connection, the same fixed per-round-trip increase takes far longer in wall-clock time to reach the same window size, purely because round trips themselves are happening less frequently.
This means Reno's recovery speed after a loss event is effectively penalized by distance, which is backwards from what's actually needed: a well-provisioned transcontinental or intercontinental link, capable of carrying a lot of data, ends up underused relative to its real capacity precisely because Reno can't climb back to a large window quickly enough after any single loss event, however minor. RFC 8312 frames CUBIC's motivation directly around "fast long-distance networks" — the exact case where Reno's constant-rate growth becomes a real bottleneck rather than a minor inefficiency.
Growing along a cubic curve instead of a straight line
CUBIC's core change is replacing Reno's linear window-growth function with a cubic function — a curve shaped like ƒ(t) = C(t − K)³ + Wmax, where Wmax is the congestion window size at the point the last loss event occurred, and K is calculated so the curve reaches Wmax at a specific point in time after the reduction. The shape of that curve, rather than the exact formula, is what matters for understanding the behavior:
CWND
| __------
Wmax|-------------------------------__/
| __--/ (plateau near Wmax,
| __-/ growth slows down)
| __--/
| __---/
| __--/
| _--/
| -/ (fast growth right after
| / the reduction)
+--------------------------------------------→ time
loss event
Right after a loss event, CUBIC grows the congestion window quickly — faster than Reno's linear increase would at that point. As the window approaches Wmax — the size at which loss last occurred — growth deliberately flattens out, since that's the size the path is already known to have had trouble sustaining. Then, if no further loss occurs, growth accelerates again past Wmax, on the theory that the previous loss event might not represent the path's true, current ceiling — network conditions change, and a limit measured once shouldn't be treated as permanent.
The practical effect of that shape is exactly what Reno's linear approach lacked: CUBIC spends very little time growing slowly near the region it already knows is close to some earlier limit, and instead concentrates its cautious behavior specifically around the point most likely to matter, while growing aggressively everywhere else — including, crucially, in the recovery period right after a reduction, where Reno was slowest and CUBIC is fastest.
Time-based growth, not round-trip-based growth
A second, easy-to-miss distinction: CUBIC's growth function is defined in terms of real elapsed time since the last congestion event, not in terms of round trips or acknowledgments received, the way both slow start and Reno's congestion avoidance are. This has a direct consequence for fairness across connections with different round-trip times. Two Reno connections sharing the same bottleneck link, one with a 10ms round trip and one with a 200ms round trip, grow their windows at very different real-world rates — the short-RTT connection completes far more round trips per second and therefore grows much faster in absolute terms, systematically claiming more of the shared link. CUBIC's time-based growth is designed to behave more consistently regardless of round-trip time, since the growth curve advances with the clock rather than with how often acknowledgments happen to arrive.
Multiplicative decrease: gentler than Reno's
CUBIC also changes how sharply the window drops on loss. Where Reno halves the congestion window (or more precisely, sets ssthresh to roughly half the flight size, as TCP Reno detailed), CUBIC's default beta parameter reduces the window to 0.7 times its value at the point of loss — a smaller, gentler cut. Combined with the fast regrowth described above, the net effect is a connection that recovers a meaningfully larger share of its previous throughput, faster, after any single loss event, compared to a Reno-based connection experiencing the identical loss on the identical path.
Checking and confirming which algorithm is running
On Linux, the active congestion-control algorithm is a kernel-wide setting, and it can be checked without changing anything:
Confirming which algorithms are even available to switch to, before considering any change, is a useful first step rather than assuming a given option is compiled in:
Per-connection behavior, rather than the kernel-wide default, can be inspected with ss for an established connection:
ESTAB 0 0 10.0.5.12:52210 93.184.216.34:443
cubic wscale:7,8 rto:212 rtt:41.2/8.6 mss:1400 cwnd:38 ssthresh:22 bytes_sent:187400 bytes_acked:187400 bytes_received:912 segs_out:142 segs_in:98 send 10.4Mbps
The cubic label here confirms this specific connection is running CUBIC (a process could, in principle, request a different algorithm via a socket option even if the system default is something else), and cwnd:38, ssthresh:22 give a live snapshot of exactly the two values TCP Reno and Slow start discussed conceptually — here they're directly readable off a real, running connection rather than inferred from a packet capture.
Practical scenario: a nightly cross-region backup that's slower than the link should allow
A nightly backup job transferring several hundred gigabytes from an on-premises server to a cloud region 4,000 kilometers away consistently achieves only a fraction of the bandwidth an iperf3 test between the same two hosts, run separately, shows the path can actually sustain. The iperf3 test itself uses default settings on both ends, and the backup job runs over a plain, unencrypted TCP transfer with no application-level throttling configured.
Checking the congestion-control algorithm on the on-premises server (the sender in this transfer) shows:
Given the path's round-trip time — measured separately at around 95ms, consistent with the physical distance involved — this matches TCP Reno's known weak spot precisely: a long-distance, high-bandwidth path where Reno's linear per-round-trip growth simply can't climb back to a large congestion window quickly enough after ordinary loss events, of which a transfer this large will encounter several over its runtime. The iperf3 comparison test, likely run from a different, more recently updated host or under different kernel defaults, was probably using CUBIC already — which is why it reached meaningfully higher throughput on the identical physical path.
Changing the congestion-control default affects every connection on the host, not just this one
net.ipv4.tcp_congestion_control is a system-wide sysctl. Before changing it in production, confirm cubic (or the intended algorithm) is actually available via sysctl net.ipv4.tcp_available_congestion_control, apply the change during a low-traffic window, and re-run both the backup job and a general health check on other services using the network afterward — a congestion-control change is a real behavioral shift for every TCP connection the host opens, not a setting scoped to one job.
Switching the sender to CUBIC and re-running the backup job the following night is the direct test of this diagnosis — a clear, repeatable improvement in achieved throughput on the same path confirms the congestion-control algorithm, not the network itself, was the bottleneck.
Practice exercises
- Using the cubic growth curve described in this article, explain why a connection that has not experienced a loss event in a long time keeps accelerating its growth rather than settling into a constant rate — what does that imply about how CUBIC treats an old loss event as time passes?
- Two TCP connections share a bottleneck link: one has a 5ms round-trip time, the other 180ms. Explain why a CUBIC-based pair of connections is expected to share the link's capacity more evenly between them than a Reno-based pair would, using the time-based-versus-round-trip-based growth distinction in this article.
- Using the
ss -tinoutput shown above, explain what it would mean, diagnostically, ifcwndwere seen dropping repeatedly back toward a small value at frequent, regular intervals on an otherwise idle path — what would that suggest is happening, independent of which congestion-control algorithm is in use?
CUBIC still relies on the same underlying signal Reno does — loss — even though it reacts to that signal differently. TCP BBR takes a more fundamental departure: instead of inferring the network's capacity mainly from when packets get dropped, it tries to measure that capacity directly.