qBittorrent 5.1.4 performance tuning with libtorrent 1.2 and Swizzin

This guide shows the qBittorrent 5.1.4, libtorrent 1.2, Swizzin, and Ubuntu tuning profile used for a high-throughput torrent test. Treat these values as a starting point: hardware, storage, network path, peer availability, and tracker behavior can all change the result.

Requirements

  • Ubuntu 24.04 server with Swizzin installed.
  • qBittorrent managed by Swizzin.
  • Root or sudo access.
  • A server where high connection counts and high file descriptor limits are acceptable.
  • A way to roll back safely before changing service, kernel, or rebuilt package settings.

Backup First

If this server is a VPS or VM with snapshots, take a snapshot before continuing. If it is bare metal, make a file-level backup and keep console or rescue access ready. These changes touch qBittorrent settings, sysctl values, systemd limits, and a rebuilt libtorrent package.

sudo systemctl stop qbittorrent@<username>
sudo install -d -m 0750 /root/canabrr-tuning-backups
sudo cp /home/<username>/.config/qBittorrent/qBittorrent.conf /root/canabrr-tuning-backups/qbittorrent.conf.$(date +%Y%m%d-%H%M%S)
sudo cp /root/libtorrent-RC_1_2.patch /root/canabrr-tuning-backups/libtorrent-RC_1_2.patch.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true

qBittorrent Settings

Edit the qBittorrent configuration file for your Swizzin user. Replace <username> with the user that runs qBittorrent.

sudo nano /home/<username>/.config/qBittorrent/qBittorrent.conf

Use these values under the qBittorrent session settings. The disk cache stays at -1 so libtorrent can manage it automatically on libtorrent 1.2.

SessionBTProtocol=TCP
SessionMaxConnections=-1
SessionMaxConnectionsPerTorrent=-1
SessionMaxUploads=-1
SessionMaxUploadsPerTorrent=-1
SessionAsyncIOThreadsCount=16
SessionConnectionSpeed=200
SessionFilePoolSize=5000
SessionDiskCacheSize=-1
SessionDiskQueueSize=10485760
SessionRequestQueueSize=1000
SessionSocketBacklogSize=3000
SessionSendBufferWatermark=10240
SessionSendBufferLowWatermark=3072
SessionSendBufferWatermarkFactor=250
SessionSocketSendBufferSize=0
SessionSocketReceiveBufferSize=0
SessionMultiConnectionsPerIp=true
sudo systemctl start qbittorrent@<username>

OS Tuning

The OS tuning enables BBR with fq, increases TCP buffer ceilings, opens the local port range, shortens FIN timeout, and sets dirty writeback thresholds for steadier disk behavior.

Run this block as one command because it writes the complete sysctl file.

sudo tee /etc/sysctl.d/90-canabrr-torrent-tuning.conf >/dev/null <<'EOF'
# Torrent throughput tuning for qBittorrent hosts.
# Reversible: remove this file and run: sysctl --system
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.tcp_mtu_probing = 1
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
vm.swappiness = 1
vm.dirty_background_bytes = 67108864
vm.dirty_bytes = 536870912
EOF
echo tcp_bbr | sudo tee /etc/modules-load.d/canabrr-tcp-bbr.conf >/dev/null

Raise the qBittorrent service limits so high connection counts are not blocked by the systemd unit.

sudo install -d /etc/systemd/system/qbittorrent@.service.d
sudo tee /etc/systemd/system/qbittorrent@.service.d/override.conf >/dev/null <<'EOF'
[Service]
LimitNOFILE=1048576
TasksMax=infinity
EOF
sudo modprobe tcp_bbr
sudo sysctl --system
sudo systemctl daemon-reload

Optional sysctl Config Generator

If you want to compare this guide with other Linux kernel tuning profiles, the ServerSpan sysctl.conf Generator can generate workload-based examples for Debian-based systems, RHEL-based systems, HDD, SSD, NVMe, different RAM sizes, CPU counts, and NIC speeds.

Treat generated sysctl values as test candidates, not production defaults. Save your current configuration first, apply changes on staging or a test server, benchmark before and after, and keep only the values that improve your workload.

Disk Tuning

Disk tuning should match the storage type and the way the server exposes that storage. A VM can be backed by NVMe on the provider side while the guest still sees a virtual disk such as /dev/sda or /dev/vda. Check what Linux sees before applying values.

lsblk -o NAME,PATH,TYPE,SIZE,MODEL,ROTA,DISC-GRAN,DISC-MAX,FSTYPE,MOUNTPOINTS
findmnt -no SOURCE,FSTYPE,OPTIONS /
cat /sys/block/<device>/queue/scheduler
cat /sys/block/<device>/queue/read_ahead_kb
cat /sys/block/<device>/queue/wbt_lat_usec

Replace <device> with the block device name without /dev/, for example sda, vda, or nvme0n1.

Tested VM Profile

This guide keeps the tested VM profile below. The guest saw a non-rotational VirtIO SCSI disk, even though the physical storage behind it was NVMe-backed.

  • ext4 root mount without live discard
  • noatime,lazytime,commit=60
  • weekly fstrim.timer
  • scheduler none
  • read_ahead_kb=4096
  • wbt_lat_usec=0
  • nr_requests=256; this VM rejected higher values
sudo systemctl enable --now fstrim.timer
sudo nano /etc/fstab

For the root ext4 mount, use options like this. Keep the existing device, label, UUID, dump, and fsck fields from your server.

LABEL=cloudimg-rootfs / ext4 errors=remount-ro,noatime,lazytime,commit=60 0 1
sudo mount -o remount,noatime,lazytime,commit=60 /
echo 4096 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/null
echo 0 | sudo tee /sys/block/<device>/queue/wbt_lat_usec >/dev/null

SSD/NVMe On VM Or VPS

For a VM or VPS, tune what the guest can control and verify provider-side disk settings separately. The guest may report a QEMU, VirtIO, or SCSI disk even when the provider storage is SSD or NVMe.

  • Prefer VirtIO SCSI or the provider recommended paravirtual disk controller.
  • Enable IO thread if your hypervisor exposes that option for the disk.
  • Enable SSD emulation and discard support at the hypervisor or provider layer when available.
  • Use weekly trim instead of continuous discard inside the guest.
  • Use none scheduler for fast virtual SSD/NVMe storage unless testing shows mq-deadline is better.
  • Use read_ahead_kb=4096 as a first torrent workload test value.
  • Disable writeback throttling only when throughput matters more than latency consistency.
cat /sys/block/<device>/queue/rotational
cat /sys/block/<device>/queue/write_cache
cat /sys/block/<device>/queue/scheduler
echo none | sudo tee /sys/block/<device>/queue/scheduler >/dev/null
echo 4096 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/null
echo 0 | sudo tee /sys/block/<device>/queue/wbt_lat_usec >/dev/null

SSD/NVMe On Bare Metal

On bare metal SSD or NVMe, Linux sees the real device directly. Keep trim scheduled, avoid live discard for torrent writes, and test scheduler/read-ahead values with the same torrent workload.

  • For NVMe, start with scheduler none.
  • For SATA SSD, test none and mq-deadline.
  • Use fstrim.timer instead of continuous discard.
  • Use noatime,lazytime,commit=60 for ext4 torrent storage.
  • Test read-ahead at 1024, 4096, and 8192.
sudo systemctl enable --now fstrim.timer
systemctl list-timers fstrim.timer --no-pager
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler >/dev/null
echo 1024 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/null
echo 4096 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/null
echo 8192 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/null

HDD On VM Or VPS

For HDD-backed VPS storage, performance can be noisy because the physical disks, cache layer, and neighbor load are outside the guest. Tune conservatively and expect random torrent workloads to hit seek limits sooner than SSD/NVMe.

  • Prefer mq-deadline when the guest reports rotational storage and the scheduler is available.
  • Try larger read-ahead such as 8192.
  • Keep noatime,lazytime,commit=60 for ext4.
  • Avoid assuming one benchmark run proves the storage is stable; repeat tests at different times.
cat /sys/block/<device>/queue/rotational
echo mq-deadline | sudo tee /sys/block/<device>/queue/scheduler >/dev/null
echo 8192 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/null

HDD On Bare Metal

Bare metal HDD servers need fewer active seeks. The best tuning is often operational: reduce the number of active torrents, keep frequently seeded files grouped, and avoid mixing heavy download bursts with upload tests.

  • Prefer mq-deadline.
  • Use larger read-ahead such as 8192 or 16384.
  • Keep noatime,lazytime,commit=60 on ext4 torrent storage.
  • Limit simultaneous active torrents when upload speed drops from seek pressure.
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler >/dev/null
echo 8192 | sudo tee /sys/block/sda/queue/read_ahead_kb >/dev/null
echo 16384 | sudo tee /sys/block/sda/queue/read_ahead_kb >/dev/null

Measured Disk Tuning Result

In one VM test with the profile above, the download average stayed about the same while the peak improved. The seed result was not useful for judging disk tuning because peer demand was low during the seed window.

MetricMeasured value
Download averageAbout 198 MiB/s
Download peakAbout 281 MiB/s
Seed/upload after completionInconclusive because peer demand was low

Treat these values as a starting point. Benchmark your own server before and after each disk change, and change only one storage setting at a time when you need a clean comparison.

libtorrent 1.2 Patch

Swizzin automatically applies /root/libtorrent-RC_1_2.patch when it builds libtorrent from the RC_1_2 branch. Put the patch in that exact path before rebuilding.

This patch changes libtorrent defaults toward faster connection ramp-up, larger queues, larger peer receive buffers, larger cache lines, one optimistic unchoke slot, and a larger alert queue.

Run this block as one command because it writes the complete patch file.

sudo tee /root/libtorrent-RC_1_2.patch >/dev/null <<'EOF'
diff --git a/src/settings_pack.cpp b/src/settings_pack.cpp
index b785117..b3025de 100644
--- a/src/settings_pack.cpp
+++ b/src/settings_pack.cpp
@@ -222,12 +222,12 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(stop_tracker_timeout, 5, nullptr),
 		SET(tracker_maximum_response_length, 1024*1024, nullptr),
 		SET(piece_timeout, 20, nullptr),
-		SET(request_timeout, 60, nullptr),
+		SET(request_timeout, 30, nullptr),
 		SET(request_queue_time, 3, nullptr),
-		SET(max_allowed_in_request_queue, 500, nullptr),
+		SET(max_allowed_in_request_queue, 1500, nullptr),
 		SET(max_out_request_queue, 500, nullptr),
 		SET(whole_pieces_threshold, 20, nullptr),
-		SET(peer_timeout, 120, nullptr),
+		SET(peer_timeout, 90, nullptr),
 		SET(urlseed_timeout, 20, nullptr),
 		SET(urlseed_pipeline_size, 5, nullptr),
 		SET(urlseed_wait_retry, 30, nullptr),
@@ -235,14 +235,14 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(max_failcount, 3, &session_impl::update_max_failcount),
 		SET(min_reconnect_time, 60, nullptr),
 		SET(peer_connect_timeout, 15, nullptr),
-		SET(connection_speed, 30, &session_impl::update_connection_speed),
-		SET(inactivity_timeout, 600, nullptr),
+		SET(connection_speed, 200, &session_impl::update_connection_speed),
+		SET(inactivity_timeout, 300, nullptr),
 		SET(unchoke_interval, 15, nullptr),
-		SET(optimistic_unchoke_interval, 30, nullptr),
+		SET(optimistic_unchoke_interval, 20, nullptr),
 		SET(num_want, 200, nullptr),
 		SET(initial_picker_threshold, 4, nullptr),
 		SET(allowed_fast_set_size, 5, nullptr),
-		SET(suggest_mode, settings_pack::no_piece_suggestions, nullptr),
+		SET(suggest_mode, settings_pack::suggest_read_cache, nullptr),
 		SET(max_queued_disk_bytes, 1024 * 1024, &session_impl::update_queued_disk_bytes),
 		SET(handshake_timeout, 10, nullptr),
 		SET(send_buffer_low_watermark, 10 * 1024, nullptr),
@@ -270,7 +270,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(seed_time_limit, 24 * 60 * 60, nullptr),
 		SET(auto_scrape_interval, 1800, nullptr),
 		SET(auto_scrape_min_interval, 300, nullptr),
-		SET(max_peerlist_size, 3000, nullptr),
+		SET(max_peerlist_size, 6000, nullptr),
 		SET(max_paused_peerlist_size, 1000, nullptr),
 		SET(min_announce_interval, 5 * 60, nullptr),
 		SET(auto_manage_startup, 60, nullptr),
@@ -279,17 +279,17 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(max_rejects, 50, nullptr),
 		SET(recv_socket_buffer_size, 0, &session_impl::update_socket_buffer_size),
 		SET(send_socket_buffer_size, 0, &session_impl::update_socket_buffer_size),
-		SET(max_peer_recv_buffer_size, 2 * 1024 * 1024, nullptr),
+		SET(max_peer_recv_buffer_size, 5 * 1024 * 1024, nullptr),
 		DEPRECATED_SET(file_checks_delay_per_block, 0, nullptr),
-		SET(read_cache_line_size, 32, nullptr),
-		SET(write_cache_line_size, 16, nullptr),
+		SET(read_cache_line_size, 64, nullptr),
+		SET(write_cache_line_size, 256, nullptr),
 		SET(optimistic_disk_retry, 10 * 60, nullptr),
-		SET(max_suggest_pieces, 16, nullptr),
+		SET(max_suggest_pieces, 64, nullptr),
 		SET(local_service_announce_interval, 5 * 60, nullptr),
 		SET(dht_announce_interval, 15 * 60, &session_impl::update_dht_announce_interval),
 		SET(udp_tracker_token_expiry, 60, nullptr),
 		DEPRECATED_SET(default_cache_min_age, 1, nullptr),
-		SET(num_optimistic_unchoke_slots, 0, nullptr),
+		SET(num_optimistic_unchoke_slots, 1, nullptr),
 		SET(default_est_reciprocation_rate, 16000, nullptr),
 		SET(increase_est_reciprocation_rate, 20, nullptr),
 		SET(decrease_est_reciprocation_rate, 3, nullptr),
@@ -315,9 +315,9 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(utp_delayed_ack, 0, nullptr),
 		SET(utp_loss_multiplier, 50, nullptr),
 		SET(mixed_mode_algorithm, settings_pack::peer_proportional, nullptr),
-		SET(listen_queue_size, 5, nullptr),
+		SET(listen_queue_size, 300, nullptr),
 		SET(torrent_connect_boost, 30, nullptr),
-		SET(alert_queue_size, 1000, &session_impl::update_alert_queue_size),
+		SET(alert_queue_size, 10000, &session_impl::update_alert_queue_size),
 		SET(max_metadata_size, 3 * 1024 * 10240, nullptr),
 		DEPRECATED_SET(hashing_threads, 1, nullptr),
 		SET(checking_mem_usage, 1024, nullptr),
@@ -351,7 +351,7 @@ constexpr int CLOSE_FILE_INTERVAL = 0;
 		SET(utp_cwnd_reduce_timer, 100, nullptr),
 		SET(max_web_seed_connections, 3, nullptr),
 		SET(resolver_cache_timeout, 1200, &session_impl::update_resolver_cache_timeout),
-		SET(send_not_sent_low_watermark, 16384, nullptr),
+		SET(send_not_sent_low_watermark, 512 * 1024, nullptr),
 		SET(rate_choker_initial_threshold, 1024, nullptr),
 		SET(upnp_lease_duration, 3600, nullptr),
 		SET(max_concurrent_http_announces, 50, nullptr),
EOF

Rebuild With Swizzin

Rebuild qBittorrent 5.1.4 with libtorrent RC_1_2. Swizzin will apply the patch above during the libtorrent build.

sudo QBITTORRENT_VERSION=5.1.4 LIBTORRENT_VERSION=RC_1_2 box upgrade qbittorrent

On a fresh Swizzin server where qBittorrent is not installed yet, use install instead of upgrade.

sudo QBITTORRENT_VERSION=5.1.4 LIBTORRENT_VERSION=RC_1_2 box install qbittorrent
sudo systemctl restart qbittorrent@<username>

Verify The Result

Confirm the service is running, qBittorrent is the expected version, libtorrent is still 1.2, BBR is active, and the listening socket is open.

qbittorrent-nox --version
dpkg -l | grep -E 'qbittorrent|libtorrent'
systemctl status qbittorrent@<username> --no-pager
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc
ss -ltnp | grep qbittorrent

Measured Result From This Test

This is one VM result from one test torrent. It is useful as a sanity check for the tuning direction, not as a universal speed promise.

MetricMeasured value
Download averageAbout 197 MiB/s
Download peakAbout 233 MiB/s
15-minute seed average after completionAbout 14.98 MiB/s
Seed peak after completionAbout 37.24 MiB/s
Final ratio after the seed windowAbout 0.284

Rollback

Rollback depends on what changed. Restore the qBittorrent config first, then remove OS tuning files, then rebuild libtorrent without the patch if needed.

sudo systemctl stop qbittorrent@<username>
sudo cp /root/canabrr-tuning-backups/qbittorrent.conf.YYYYMMDD-HHMMSS /home/<username>/.config/qBittorrent/qBittorrent.conf
sudo rm -f /etc/sysctl.d/90-canabrr-torrent-tuning.conf /etc/modules-load.d/canabrr-tcp-bbr.conf
sudo rm -rf /etc/systemd/system/qbittorrent@.service.d
sudo sysctl --system
sudo systemctl daemon-reload
sudo rm -f /root/libtorrent-RC_1_2.patch
sudo QBITTORRENT_VERSION=5.1.4 LIBTORRENT_VERSION=RC_1_2 box upgrade qbittorrent
sudo systemctl start qbittorrent@<username>

Next Step

After the service is stable, test with the same torrent, same save path, same measurement window, and the same qBittorrent settings. Change only one tuning area at a time so the result is comparable.