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=4096wbt_lat_usec=0nr_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
nonescheduler for fast virtual SSD/NVMe storage unless testing showsmq-deadlineis better. - Use
read_ahead_kb=4096as 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
noneandmq-deadline. - Use
fstrim.timerinstead of continuousdiscard. - Use
noatime,lazytime,commit=60for ext4 torrent storage. - Test read-ahead at
1024,4096, and8192.
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-deadlinewhen the guest reports rotational storage and the scheduler is available. - Try larger read-ahead such as
8192. - Keep
noatime,lazytime,commit=60for 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
8192or16384. - Keep
noatime,lazytime,commit=60on 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.
| Metric | Measured value |
|---|---|
| Download average | About 198 MiB/s |
| Download peak | About 281 MiB/s |
| Seed/upload after completion | Inconclusive 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.
| Metric | Measured value |
|---|---|
| Download average | About 197 MiB/s |
| Download peak | About 233 MiB/s |
| 15-minute seed average after completion | About 14.98 MiB/s |
| Seed peak after completion | About 37.24 MiB/s |
| Final ratio after the seed window | About 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.