<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Networking on Terminal Thoughts</title><link>https://thoughts.greyh.at/tags/networking/</link><description>Recent content in Networking on Terminal Thoughts</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>quest@mac.com (quest)</managingEditor><webMaster>quest@mac.com (quest)</webMaster><copyright>© 2026 quest</copyright><lastBuildDate>Sun, 28 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://thoughts.greyh.at/tags/networking/index.xml" rel="self" type="application/rss+xml"/><item><title>Saturating 10 Gigabit on Linux</title><link>https://thoughts.greyh.at/posts/saturating-10-gigabit/</link><pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate><author>quest@mac.com (quest)</author><guid>https://thoughts.greyh.at/posts/saturating-10-gigabit/</guid><description>Wringing full speed out of a network used to be a niche pursuit, the stuff of 10G home labs and datacenter transfers. That&amp;rsquo;s changing fast. Multi-gig fiber is landing in homes everywhere, even Hawaiian Telecom is selling 3 Gig out here on the Big Island. A fast link is no longer just the wire between your own machines; it&amp;rsquo;s the wire to the internet too. And Linux still ships network defaults sized for a slower era, so on a fast link, especially a long one, they leave throughput unused. Here&amp;rsquo;s the sysctl set I run, what each knob does and where it actually matters, and the single-stream iperf3 result over 20 feet of in-wall Cat 5e: 9.75 Gbit/s.</description><content>&lt;p&gt;Wringing full speed out of a network used to be a niche pursuit, the stuff of 10G home labs and datacenter transfers. That&amp;rsquo;s changing fast. Multi-gig fiber is landing in homes everywhere, even Hawaiian Telecom is selling 3 Gig out here on the Big Island. A fast link is no longer just the wire between your own machines; it&amp;rsquo;s the wire to the internet too. And Linux still ships network defaults sized for a slower era, so on a fast link, especially a long one, they leave throughput unused. Here&amp;rsquo;s the sysctl set I run, what each knob does and where it actually matters, and the single-stream &lt;a href="https://github.com/esnet/iperf"&gt;iperf3&lt;/a&gt; result over 20 feet of in-wall Cat 5e: 9.75 Gbit/s.&lt;/p&gt;
&lt;p&gt;This started on my own network. I&amp;rsquo;d built a 10-gigabit link between my laptop and my NAS, and I wanted to know how much of it I was really getting, and whether it&amp;rsquo;d be ready when my internet connection catches up. That meant a look at the kernel&amp;rsquo;s network defaults, which are more conservative than you&amp;rsquo;d expect. I&amp;rsquo;ll walk them in groups, and since a local LAN and a long-haul link lean on very different parts of the list, I&amp;rsquo;ll flag which is which as we go.&lt;/p&gt;
&lt;h2 id="congestion-control-bbr-and-fq"&gt;Congestion Control: BBR and fq&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.default_qdisc&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;fq&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_congestion_control&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;bbr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Congestion control is the algorithm that decides how fast a TCP sender pushes data and how it backs off when the network pushes back. The long-time default, CUBIC, treats packet loss as the signal to slow down. On a clean local wire that&amp;rsquo;s fine, but on a path with real distance or the occasional dropped packet, it retreats hard and leaves the pipe half full.&lt;/p&gt;
&lt;p&gt;Enter BBR. Rather than waiting for loss, it measures the path directly - the bottleneck bandwidth and the round-trip time - and paces itself to sit right at that limit. On a long or lossy link it holds throughput that CUBIC would surrender, which is exactly where a fat pipe over distance struggles without it.&lt;/p&gt;
&lt;p&gt;That pacing is the key, and it wants a qdisc that can space packets out evenly instead of letting them burst. That qdisc is &lt;code&gt;fq&lt;/code&gt; (&amp;ldquo;fair queue&amp;rdquo;), so &lt;code&gt;default_qdisc = fq&lt;/code&gt; and &lt;code&gt;bbr&lt;/code&gt; are the standard pairing: BBR sets the pace, &lt;code&gt;fq&lt;/code&gt; keeps it. Both have been in the mainline kernel for years; on anything current you just switch them on.&lt;/p&gt;
&lt;p&gt;Where it helps, honestly: on your LAN, with sub-millisecond latency and no loss, CUBIC already saturates 10G and BBR won&amp;rsquo;t beat it by much. This pair pays off the moment distance enters the path, a transfer to a server across the country, or any link that isn&amp;rsquo;t pristine. It costs nothing locally and rescues the long-haul case, so it stays.&lt;/p&gt;
&lt;h2 id="socket-buffers"&gt;Socket Buffers&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.rmem_default&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1048576&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.rmem_max&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;67108864&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.wmem_default&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1048576&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.wmem_max&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;67108864&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.optmem_max&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;65536&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_rmem&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;4096 1048576 67108864&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_wmem&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;4096 65536 67108864&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.udp_rmem_min&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8192&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.udp_wmem_min&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8192&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A socket buffer is the memory the kernel holds for data in flight, one buffer for sending and one for receiving. They matter for throughput because of a hard limit in TCP: a sender can have only one window of unacknowledged data on the wire at a time, and that window can&amp;rsquo;t exceed the buffer. If the buffer is too small, the sender fills the window and then stalls, waiting for acknowledgments to come back before it can send any more.&lt;/p&gt;
&lt;p&gt;How big does it need to be? At least the bandwidth-delay product (BDP): the link&amp;rsquo;s bandwidth times its round-trip time, which is the amount of data in flight needed to keep the pipe full. At 10 Gbit/s over a 50 ms path that works out to about 60 MiB, which is where the 64 MiB ceiling (&lt;code&gt;67108864&lt;/code&gt; bytes) comes from. Size the buffer under the BDP and throughput is capped no matter how fast the link.&lt;/p&gt;
&lt;p&gt;The three-number values, &lt;code&gt;tcp_rmem&lt;/code&gt; and &lt;code&gt;tcp_wmem&lt;/code&gt;, are min, default, and max. The kernel autotunes each connection between min and max, starting at the default and growing the buffer only as the connection demands it. So 64 MiB is a ceiling, not a reservation - a busy long-haul transfer can climb to it while a quiet connection stays small, and you get the headroom without paying for it on every socket. The &lt;code&gt;rmem_max&lt;/code&gt;/&lt;code&gt;wmem_max&lt;/code&gt; pair sets that same ceiling for the core, and &lt;code&gt;optmem_max&lt;/code&gt; covers the small ancillary buffer used for control messages.&lt;/p&gt;
&lt;p&gt;The two &lt;code&gt;udp_*_min&lt;/code&gt; values raise the floor below which UDP buffers won&amp;rsquo;t shrink under memory pressure. UDP doesn&amp;rsquo;t autotune the way TCP does, so the floor keeps it steady, and since the large buffers above apply to UDP sockets too, the same settings quietly help QUIC, which rides on UDP.&lt;/p&gt;
&lt;p&gt;Honestly, on your LAN almost none of this is in play. At sub-millisecond latency the BDP is a few hundred kilobytes, so the autotuner never climbs near 64 MiB and the stock defaults would fill the link just fine. The big ceilings are insurance for the high-latency case, pulling 10G from across the country, where without them the window tops out and you&amp;rsquo;d never see more than a slice of the pipe.&lt;/p&gt;
&lt;h2 id="queues-and-backlogs"&gt;Queues and Backlogs&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.netdev_max_backlog&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;16384&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_max_syn_backlog&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8192&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.core.somaxconn&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8192&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These three set how many packets or connections can wait in line at points where the network stack hands work off. Leave them too shallow and a burst overflows the queue, which means dropped packets or refused connections.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;netdev_max_backlog&lt;/code&gt; is the one that matters most at 10G. When packets arrive faster than the CPU can lift them into the stack, they pile up in this per-interface queue. The default of 1000 was sized for slower links; a 10-gigabit interface can fill that in a blink during a burst, and anything past the edge gets dropped. Raising it to 16384 gives the stack room to catch up before it starts discarding.&lt;/p&gt;
&lt;p&gt;The other two are about accepting connections, and they matter when the box is a busy server rather than a client. &lt;code&gt;tcp_max_syn_backlog&lt;/code&gt; is the queue of half-open connections still completing their handshake, which also gives some cushion against a SYN flood. &lt;code&gt;somaxconn&lt;/code&gt; caps the queue of established connections waiting for the application to pick them up. That one is only a ceiling: the program also has to ask for a backlog that large when it calls &lt;code&gt;listen()&lt;/code&gt;, or it gets whatever it requested instead.&lt;/p&gt;
&lt;p&gt;For a single transfer none of the connection queues come into play, but they cost nothing idle and save you the day a service suddenly fields a flood of clients. The backlog bump is the one doing the real work on the 10G receive path.&lt;/p&gt;
&lt;h2 id="connection-behavior"&gt;Connection Behavior&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_fastopen&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.ip_local_port_range&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;32768 65535&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_tw_reuse&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_max_tw_buckets&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;2000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_slow_start_after_idle&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This group is less about raw throughput and more about how connections open, close, and reopen, which matters most when a box churns through a lot of them.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tcp_slow_start_after_idle = 0&lt;/code&gt; is the one I&amp;rsquo;d keep regardless. Normally, when a connection sits idle for a moment, the kernel forgets how fast it was going and slow-starts from scratch the next time it sends. Disabling that lets a long-lived connection pick up where it left off, which helps anything bursty: a persistent connection between transfers, a video stream that fills its buffer and pauses. It pairs especially well with BBR, which keeps its own read on the path rather than rebuilding from zero.&lt;/p&gt;
&lt;p&gt;The next three fight port and connection exhaustion on a box that opens many short-lived connections. &lt;code&gt;ip_local_port_range&lt;/code&gt; sets the pool of ephemeral ports for outbound connections; starting it at &lt;code&gt;32768&lt;/code&gt; keeps it clear of the registered service ports below, so an outbound connection never grabs a port a local service wanted. &lt;code&gt;tcp_tw_reuse&lt;/code&gt; lets the kernel safely reuse a connection still lingering in &lt;code&gt;TIME_WAIT&lt;/code&gt; for a new outbound one, using timestamps to be sure it&amp;rsquo;s safe. (This is the modern, safe reuse, not the old &lt;code&gt;tcp_tw_recycle&lt;/code&gt;, which mangled connections behind NAT and was removed from the kernel years ago.) &lt;code&gt;tcp_max_tw_buckets&lt;/code&gt; caps how many &lt;code&gt;TIME_WAIT&lt;/code&gt; sockets the kernel will hold; it&amp;rsquo;s only a ceiling, and with reuse on you&amp;rsquo;ll rarely come near two million, but it keeps the table from growing without bound.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tcp_fastopen = 3&lt;/code&gt; enables TCP Fast Open for both clients and servers, letting a little data ride along in the SYN to save a round trip at setup. I&amp;rsquo;ll be straight: in 2026 it&amp;rsquo;s mostly inert. Browsers walked away from it, plenty of networks strip the option in transit, and it does nothing for a bulk transfer that&amp;rsquo;s already up and running. It&amp;rsquo;s harmless to leave on, so it stays, but I wouldn&amp;rsquo;t expect much from it.&lt;/p&gt;
&lt;h2 id="keepalives"&gt;Keepalives&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_keepalive_time&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_keepalive_intvl&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_keepalive_probes&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When a connection goes quiet, how does either end know the other is still there? TCP keepalive answers that by sending the occasional probe down an idle connection. If enough probes go unanswered, the kernel declares the peer gone and tears the connection down.&lt;/p&gt;
&lt;p&gt;The three values shape that timing. &lt;code&gt;tcp_keepalive_time = 60&lt;/code&gt; is how long a connection sits idle before the first probe goes out, down from a default of 7200 seconds, a full two hours. &lt;code&gt;tcp_keepalive_intvl = 10&lt;/code&gt; is the gap between probes, and &lt;code&gt;tcp_keepalive_probes = 6&lt;/code&gt; is how many can go unanswered before the connection is declared dead. Together they spot a dead peer in about two minutes (60 seconds idle, then six probes ten seconds apart) instead of the default two-plus hours.&lt;/p&gt;
&lt;p&gt;Faster detection does two useful things: it reaps dead connections quickly instead of leaving them to rot, and it keeps idle connections from being silently dropped by NAT and firewalls, which often expire a mapping after a few minutes of silence. A probe every so often keeps that path warm.&lt;/p&gt;
&lt;p&gt;One caveat to be honest about: keepalive only applies to connections that opt in with &lt;code&gt;SO_KEEPALIVE&lt;/code&gt;, and plenty of applications manage liveness themselves with their own heartbeats. So this won&amp;rsquo;t touch every connection on the box. Where it does apply, the cost is a stray packet now and then, which is nothing.&lt;/p&gt;
&lt;h2 id="jumbo-frames-and-mtu-probing"&gt;Jumbo Frames and MTU Probing&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;net.ipv4.tcp_mtu_probing&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last knob deals with packet size, and it pairs with a setting that lives outside sysctl entirely: jumbo frames.&lt;/p&gt;
&lt;p&gt;By default an Ethernet frame carries up to 1500 bytes. Every frame has fixed overhead, headers and inter-frame gaps, so smaller frames mean a larger share of the wire spent on overhead instead of data. A standard 1500-byte MTU tops a 10G link out around 9.4 Gbit/s of actual throughput for that reason. Jumbo frames raise the MTU to 9000 bytes, six times the payload per frame for the same overhead, which lifts the ceiling closer to the full 10 gigabit. On my LAN it&amp;rsquo;s the single biggest reason a transfer clears 9.4. Jumbo frames aren&amp;rsquo;t a sysctl; you set the MTU on the interface itself, and crucially on every device in the path, since the smallest MTU along the way wins.&lt;/p&gt;
&lt;p&gt;That &amp;ldquo;every device in the path&amp;rdquo; requirement is where &lt;code&gt;tcp_mtu_probing&lt;/code&gt; comes in. Normally the kernel learns the largest packet a path can carry through Path MTU Discovery, which leans on ICMP messages from routers. Plenty of firewalls drop those messages, creating a black hole: oversized packets vanish silently and the connection stalls instead of getting told to back off. With probing enabled, TCP figures out the working size on its own by testing, and recovers. Value &lt;code&gt;1&lt;/code&gt; keeps it conservative, kicking in only when a stall is detected, which is exactly what you want as a safety net behind jumbo frames rather than an always-on cost.&lt;/p&gt;
&lt;h2 id="the-complete-set"&gt;The Complete Set&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s every knob from the walkthrough. Here it is in one place.&lt;/p&gt;
&lt;div class="collapsable-code"&gt;
&lt;input id="1" type="checkbox" checked /&gt;
&lt;label for="1"&gt;
&lt;span class="collapsable-code__language"&gt;ini&lt;/span&gt;
&lt;span class="collapsable-code__title"&gt;/etc/sysctl.d/99-net.conf&lt;/span&gt;
&lt;span class="collapsable-code__toggle" data-label-expand="Show" data-label-collapse="Hide"&gt;&lt;/span&gt;
&lt;/label&gt;
&lt;pre class="language-ini" &gt;&lt;code&gt;
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.core.rmem_default = 1048576
net.core.rmem_max = 67108864
net.core.wmem_default = 1048576
net.core.wmem_max = 67108864
net.core.optmem_max = 65536
net.ipv4.tcp_rmem = 4096 1048576 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 32768 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 6
net.ipv4.tcp_mtu_probing = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="applying-the-settings"&gt;Applying the Settings&lt;/h2&gt;
&lt;p&gt;Applying these is straightforward. Drop the file into &lt;code&gt;/etc/sysctl.d/99-net.conf&lt;/code&gt;. The &lt;code&gt;99-&lt;/code&gt; prefix makes it load last, so it wins over anything your distro or a package dropped in earlier. Apply it without a reboot:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo sysctl --system
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That reloads every file under &lt;code&gt;/etc/sysctl.d/&lt;/code&gt; and echoes each setting as it applies it. To confirm one took, read it back:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sysctl net.ipv4.tcp_congestion_control
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If it comes back &lt;code&gt;bbr&lt;/code&gt;, you&amp;rsquo;re set!&lt;/p&gt;
&lt;h2 id="verifying-it-worked"&gt;Verifying It Worked&lt;/h2&gt;
&lt;p&gt;Numbers settle it, so the real test is a throughput run. &lt;code&gt;iperf3&lt;/code&gt; measures it end to end: run a server on one machine and point a client at it from another. No second machine? A &lt;a href="https://iperf3serverlist.net/"&gt;list of public iperf3 servers&lt;/a&gt; lets you test against the internet instead, which is the better gauge of your long-haul tuning anyway.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iperf3 -s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;❯ iperf3 -c 192.168.1.25
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connecting to host 192.168.1.25, port &lt;span style="color:#ae81ff"&gt;5201&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; local 192.168.1.7 port &lt;span style="color:#ae81ff"&gt;34734&lt;/span&gt; connected to 192.168.1.25 port &lt;span style="color:#ae81ff"&gt;5201&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; ID&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Interval Transfer Bitrate Retr Cwnd
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 0.00-1.00 sec 1.12 GBytes 9.65 Gbits/sec &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;699&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 1.00-2.00 sec 1.13 GBytes 9.74 Gbits/sec &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;533&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 2.00-3.00 sec 1.14 GBytes 9.76 Gbits/sec &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;516&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 3.00-4.00 sec 1.14 GBytes 9.77 Gbits/sec &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;489&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 4.00-5.00 sec 1.13 GBytes 9.73 Gbits/sec &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;507&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 5.00-6.00 sec 1.14 GBytes 9.79 Gbits/sec &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;551&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 6.00-7.00 sec 1.13 GBytes 9.74 Gbits/sec &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;533&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 7.00-8.00 sec 1.14 GBytes 9.76 Gbits/sec &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;568&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 8.00-9.00 sec 1.14 GBytes 9.80 Gbits/sec &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;568&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 9.00-10.00 sec 1.14 GBytes 9.77 Gbits/sec &lt;span style="color:#ae81ff"&gt;11&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;489&lt;/span&gt; KBytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- - - - - - - - - - - - - - - - - - - - - - - - -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; ID&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Interval Transfer Bitrate Retr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 0.00-10.00 sec 11.4 GBytes 9.75 Gbits/sec &lt;span style="color:#ae81ff"&gt;55&lt;/span&gt; sender
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt; 5&lt;span style="color:#f92672"&gt;]&lt;/span&gt; 0.00-10.00 sec 11.4 GBytes 9.75 Gbits/sec receiver
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iperf Done.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A single stream is the demanding case, and it already clears 9.75. Push to eight parallel streams (&lt;code&gt;iperf3 -P 8&lt;/code&gt;) and it climbs to 9.88 Gbit/s, essentially the wire&amp;rsquo;s ceiling.&lt;/p&gt;
&lt;p&gt;One detail in the single-stream output is a nice callback to the buffers section: the congestion window (&lt;code&gt;Cwnd&lt;/code&gt;) never climbs past roughly 700 KB, nowhere near those 64 MiB ceilings. On a sub-millisecond LAN it doesn&amp;rsquo;t need to. All that buffer just sits unused, waiting for a longer path.&lt;/p&gt;
&lt;p&gt;Those numbers came off my own gear, and the path behind them is worth describing because none of it is exotic. The client isn&amp;rsquo;t even a desktop: it&amp;rsquo;s a &lt;a href="https://frame.work/laptop13"&gt;Framework Laptop 13&lt;/a&gt;, and the 10G arrives from a &lt;a href="https://www.caldigit.com/thunderbolt-5-dock-ts5-plus/"&gt;CalDigit TS5 Plus&lt;/a&gt; dock, where the NIC actually lives. The dock is Thunderbolt 5, but the laptop&amp;rsquo;s Thunderbolt 4 port caps the bus at 40 Gbit/s, still far more than a 10G link needs. From there it&amp;rsquo;s an in-wall run of 20 to 25 feet of Cat 5e into a &lt;a href="https://www.tp-link.com/us/business-networking/unmanaged-switch/tl-sx105/v1/"&gt;TP-Link TL-SX105&lt;/a&gt; switch at each end, then a Synology DS1621xs+ with a built-in 10G port running the server.&lt;/p&gt;
&lt;p&gt;The Cat 5e is the surprising part. It isn&amp;rsquo;t rated for 10GBASE-T; the standard wants Cat 6a, with Cat 6 for shorter reaches. But those limits come from crosstalk and signal loss that build with length, and over a short run Cat 5e carries 10G without complaint. The link negotiated the full rate, no drop to 5G or 2.5G. Don&amp;rsquo;t take this as &amp;ldquo;Cat 5e is fine for 10G&amp;rdquo; down a long pull or a packed bundle, where it won&amp;rsquo;t be, but at desk distances you may not need to rewire a thing.&lt;/p&gt;
&lt;p&gt;This also wasn&amp;rsquo;t a clean-room benchmark. The NAS on the other end is my everyday one, busy and not idled for the test, and it still hit line rate. Real-world conditions, real-world number.&lt;/p&gt;
&lt;p&gt;One honest clarification on what&amp;rsquo;s measured: iperf3 runs memory to memory, so it tests the wire, not the disks. The 9.75 Gbit/s is the network&amp;rsquo;s ceiling, not what a file copy to the array will sustain, which is a separate and slower story. But the wire is what this file set out to fill, and it&amp;rsquo;s full.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;A 10-gigabit link is easy to buy and, it turns out, easy to fill, once the kernel stops playing it safe. Most of what&amp;rsquo;s in the file is insurance: buffers and BBR for the day a transfer has to cross real distance, queues and keepalives for a box that gets busy. On a quiet local LAN the win that actually moved the needle was humbler, jumbo frames, and the stock defaults handled more of the rest than I expected.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the honest shape of it. Drop the file in, run your own &lt;code&gt;iperf3&lt;/code&gt;, and read the number against your own path: a short hop to a NAS is one story, a haul across the internet another. None of these settings conjure bandwidth that isn&amp;rsquo;t there. They just make sure that when it is, the kernel actually uses it.&lt;/p&gt;
&lt;p&gt;Mine does, even over twenty-odd feet of Cat 5e the spec sheet says shouldn&amp;rsquo;t manage it. The pipe was never the problem. It was just waiting for permission.&lt;/p&gt;</content></item></channel></rss>