{"id":969,"date":"2026-10-31T09:00:00","date_gmt":"2026-10-31T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=969"},"modified":"2026-10-31T09:00:00","modified_gmt":"2026-10-31T09:00:00","slug":"enable-bbr-debian-12","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/10\/31\/enable-bbr-debian-12\/","title":{"rendered":"How to Enable BBR on Debian 12 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Enable BBR on Debian 12 to make new TCP connections use BBR with the <code>fq<\/code> packet scheduler. The change takes about five minutes, applies without opening a port, and can be retained across reboots.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>A Debian 12 (Bookworm) VPS with a kernel newer than 4.9; the standard Debian 12 kernel is in the 6.1 series.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a normal account that can run commands with <code>sudo<\/code>.<\/li>\n\n\n<li>Permission to load kernel modules and write files below <code>\/etc<\/code>; an unprivileged container may not provide these capabilities.<\/li>\n\n\n<li>No domain, web server, extra RAM, or extra disk is needed because BBR is a kernel-level TCP setting.<\/li>\n\n\n<li>If you need the server first, start with a <a href=\"https:\/\/vpslake.com\/buy-vps\">Linux VPS from VPSLake<\/a> and install Debian 12.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">BBR changes TCP congestion control and packet pacing. It cannot raise your plan\u2019s bandwidth limit, and the result depends on the route, the remote peer, and the workload.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm Debian 12 and inspect the current TCP settings<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Check the release and running kernel before changing anything so a provider-supplied image or custom kernel does not surprise you.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat \/etc\/debian_version\n. \/etc\/os-release &amp;&amp; printf '%s\\n' \"$PRETTY_NAME\"\nuname -r\nsysctl net.ipv4.tcp_available_congestion_control\nsysctl net.ipv4.tcp_congestion_control<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The release should identify Debian GNU\/Linux 12 (Bookworm), and the kernel will normally start with <code>6.1<\/code>. A new installation commonly uses <code>cubic<\/code>; the available-algorithm list may not include BBR until its module is loaded.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>12.x\nDebian GNU\/Linux 12 (bookworm)\n6.1.0-...-amd64\nnet.ipv4.tcp_available_congestion_control = reno cubic\nnet.ipv4.tcp_congestion_control = cubic<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The point release, kernel revision, and algorithm order vary. Keep a newer provider kernel if it is working; the important requirement is that it exposes BBR.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Load BBR for this boot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The kernel must register BBR before a sysctl can select it, so load the module and check the list immediately.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo modprobe tcp_bbr\nsysctl net.ipv4.tcp_available_congestion_control<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The result should contain <code>bbr<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>net.ipv4.tcp_available_congestion_control = reno cubic bbr<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If BBR was already listed, <code>modprobe<\/code> leaves it ready and the second command confirms that the running kernel can use it. The order of the names is not significant.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Save the settings so they survive a reboot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Put the module request and sysctl values in separate local files; Debian\u2019s boot services can then restore both pieces without changing unrelated configuration.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tee \/etc\/modules-load.d\/tcp-bbr.conf &gt;\/dev\/null &lt;&lt;'EOF'\ntcp_bbr\nEOF\nsudo tee \/etc\/sysctl.d\/99-bbr-debian12.conf &gt;\/dev\/null &lt;&lt;'EOF'\nnet.core.default_qdisc = fq\nnet.ipv4.tcp_congestion_control = bbr\nEOF\nsudo sysctl -p \/etc\/sysctl.d\/99-bbr-debian12.conf<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first file asks the boot-time module loader to make BBR available. The second selects BBR for new TCP connections and sets fair queueing as the default scheduler; <code>sysctl -p<\/code> applies it now instead of waiting for a reboot.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>net.core.default_qdisc = fq\nnet.ipv4.tcp_congestion_control = bbr<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Debian reads local kernel-parameter files from <code>\/etc\/sysctl.d\/<\/code>; the <a href=\"https:\/\/www.debian.org\/doc\/manuals\/debian-reference\/ch05.en.html\" target=\"_blank\" rel=\"noopener\">Debian Reference<\/a> documents this configuration model.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Check the queue attached to the live network interface<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>net.core.default_qdisc<\/code> controls the default for interfaces created later, but the interface already carrying your SSH session can still have another root queue.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ip -o route show default\nMAIN_IFACE=$(ip -o route show default | awk '{print $5; exit}')\nprintf '%s\\n' \"$MAIN_IFACE\"\ntc qdisc show dev \"$MAIN_IFACE\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The route output identifies the default device, often <code>ens3<\/code> or <code>eth0<\/code>. If the final command does not show <code>qdisc fq<\/code>, replace the live root queue for the current boot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo tc qdisc replace dev \"$MAIN_IFACE\" root fq\ntc qdisc show dev \"$MAIN_IFACE\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A successful check contains <code>qdisc fq<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default via 203.0.113.1 dev ens3 proto dhcp src 203.0.113.20 metric 100\nens3\nqdisc fq 0: root refcnt 2 limit 10000p buckets 1024<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your interface name and queue details will differ. This <code>tc<\/code> replacement is runtime state; the saved <code>default_qdisc<\/code> value handles future interface initialization and the next boot. Do not bring the network profile down over SSH unless you have the VPS control panel console available.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify BBR on Debian 12<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run the complete check below to confirm the active algorithm, persistent files, module availability, and qdisc on the interface used by the default route.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MAIN_IFACE=$(ip -o route show default | awk '{print $5; exit}')\nsysctl net.ipv4.tcp_congestion_control net.core.default_qdisc\nsysctl net.ipv4.tcp_available_congestion_control\ngrep -Ev '^[[:space:]]*(#|$)' \/etc\/sysctl.d\/99-bbr-debian12.conf\ngrep -Fx 'tcp_bbr' \/etc\/modules-load.d\/tcp-bbr.conf\ntc qdisc show dev \"$MAIN_IFACE\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You want <code>tcp_congestion_control = bbr<\/code>, <code>default_qdisc = fq<\/code>, <code>bbr<\/code> in the available list, both assignments from <code>99-bbr-debian12.conf<\/code>, the line <code>tcp_bbr<\/code>, and <code>qdisc fq<\/code> for the active device. These values affect connections created after the change, so reconnect SSH or restart a long-lived service before comparing traffic. The <a href=\"https:\/\/docs.kernel.org\/networking\/ip-sysctl.html\" target=\"_blank\" rel=\"noopener\">Linux kernel IP sysctl reference<\/a> explains the TCP parameters used here.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Tune Debian TCP networking\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">Linux VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Tune Debian TCP networking<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Run Debian 12 workloads on a VPS ready for persistent BBR settings.<\/p>\n\t<\/div>\n\t<a class=\"vl-cta-banner__button\" href=\"\/buy-vps\" aria-label=\"View Linux VPS plans\">View Linux VPS plans<\/a>\n<\/aside>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>tc: command not found<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The minimal Debian image may not include <code>iproute2<\/code>, which provides both <code>ip<\/code> and <code>tc<\/code>. Install it, then repeat Step 4:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt-get update\nsudo apt-get install -y iproute2<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>bbr<\/code> is missing from the available algorithm list<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The running kernel has not registered BBR. Try loading the module once; if <code>modprobe<\/code> reports that <code>tcp_bbr<\/code> is missing, install Debian\u2019s standard kernel meta-package and reboot into it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo modprobe tcp_bbr\nsysctl net.ipv4.tcp_available_congestion_control\nsudo apt-get update\nsudo apt-get install -y linux-image-amd64\nsudo reboot<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After reconnecting, run Step 2 again. If the machine still boots a provider-managed custom kernel, use the VPS control panel console and request a Debian 12 kernel with BBR support.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>RTNETLINK answers: Operation not permitted<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This usually means the VPS is an unprivileged container without the capability needed to replace a qdisc. The TCP sysctl may still work, but the live <code>fq<\/code> queue cannot be changed from inside that container; ask the provider for a full VPS or traffic-control permission.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The server returns to <code>cubic<\/code> after reboot<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Another sysctl file may be loaded after your setting, or the module was not loaded early enough. Search the active configuration, then reload the module and the Debian 12 file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -RnsE 'tcp_congestion_control|default_qdisc' \/etc\/sysctl.conf \/etc\/sysctl.d \/usr\/lib\/sysctl.d 2&gt;\/dev\/null\nsudo systemctl restart systemd-modules-load\nsudo sysctl -p \/etc\/sysctl.d\/99-bbr-debian12.conf\nsysctl net.ipv4.tcp_available_congestion_control net.ipv4.tcp_congestion_control<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the local file name beginning with <code>99-<\/code> so it loads late in the normal sysctl ordering. If the provider kernel does not expose BBR, install and select Debian\u2019s standard kernel as described above.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Why is <code>fq<\/code> used with BBR?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">BBR estimates capacity and paces packets, while <code>fq<\/code> provides per-flow fair queueing and lets the kernel pace those packets cleanly. Setting <code>net.core.default_qdisc<\/code> selects <code>fq<\/code> for newly initialized interfaces; Step 4 checks whether the current interface also needs a live replacement.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why can BBR be available but not active?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Loading <code>tcp_bbr<\/code> only registers the algorithm. The <code>net.ipv4.tcp_congestion_control = bbr<\/code> assignment is what selects it for new TCP connections, so check that both the live sysctl and <code>99-bbr-debian12.conf<\/code> contain BBR.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I enable BBR inside a Docker or LXC container?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Only when the container shares a host kernel that exposes BBR and grants the needed module\/sysctl and traffic-control permissions. If <code>modprobe<\/code> or <code>tc<\/code> is denied, enable BBR on the host or use a VPS with full kernel networking access.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Will an existing SSH connection switch to BBR?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The setting is selected when TCP connections are created. Your current SSH session may keep its original congestion-control state; reconnect SSH or restart the service you are evaluating after verification succeeds.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Why is `fq` used with BBR?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"BBR estimates capacity and paces packets, while `fq` provides per-flow fair queueing and lets the kernel pace those packets cleanly. Setting `net.core.default_qdisc` selects `fq` for newly initialized interfaces; Step 4 checks whether the current interface also needs a live replacement.\"}}, {\"@type\": \"Question\", \"name\": \"Why can BBR be available but not active?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Loading `tcp_bbr` only registers the algorithm. The `net.ipv4.tcp_congestion_control = bbr` assignment is what selects it for new TCP connections, so check that both the live sysctl and `99-bbr-debian12.conf` contain BBR.\"}}, {\"@type\": \"Question\", \"name\": \"Can I enable BBR inside a Docker or LXC container?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Only when the container shares a host kernel that exposes BBR and grants the needed module\/sysctl and traffic-control permissions. If `modprobe` or `tc` is denied, enable BBR on the host or use a VPS with full kernel networking access.\"}}, {\"@type\": \"Question\", \"name\": \"Will an existing SSH connection switch to BBR?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"The setting is selected when TCP connections are created. Your current SSH session may keep its original congestion-control state; reconnect SSH or restart the service you are evaluating after verification succeeds.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Learn how to enable bbr on debian 12 with persistent kernel settings, live fq queue checks, verification commands, and fixes for common VPS kernel errors.","protected":false},"author":1,"featured_media":970,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"csco_singular_sidebar":"","csco_page_header_type":"","csco_page_load_nextpost":"","footnotes":""},"categories":[5,9],"tags":[113,97,114,115],"class_list":["post-969","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-bbr","tag-debian-12","tag-linux-networking","tag-vps-tuning","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/969","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/comments?post=969"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/969\/revisions"}],"predecessor-version":[{"id":3818,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/969\/revisions\/3818"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/970"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=969"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=969"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=969"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}