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-backupssudo 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 || trueqBittorrent 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.confUse 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=truesudo 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
EOFecho tcp_bbr | sudo tee /etc/modules-load.d/canabrr-tcp-bbr.conf >/dev/nullRaise the qBittorrent service limits so high connection counts are not blocked by the systemd unit.
sudo install -d /etc/systemd/system/qbittorrent@.service.dsudo tee /etc/systemd/system/qbittorrent@.service.d/override.conf >/dev/null <<'EOF'
[Service]
LimitNOFILE=1048576
TasksMax=infinity
EOFsudo modprobe tcp_bbrsudo sysctl --systemsudo systemctl daemon-reloadOptional 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,MOUNTPOINTSfindmnt -no SOURCE,FSTYPE,OPTIONS /cat /sys/block/<device>/queue/schedulercat /sys/block/<device>/queue/read_ahead_kbcat /sys/block/<device>/queue/wbt_lat_usecReplace <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.timersudo nano /etc/fstabFor 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 1sudo mount -o remount,noatime,lazytime,commit=60 /echo 4096 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/nullecho 0 | sudo tee /sys/block/<device>/queue/wbt_lat_usec >/dev/nullSSD/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/rotationalcat /sys/block/<device>/queue/write_cachecat /sys/block/<device>/queue/schedulerecho none | sudo tee /sys/block/<device>/queue/scheduler >/dev/nullecho 4096 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/nullecho 0 | sudo tee /sys/block/<device>/queue/wbt_lat_usec >/dev/nullSSD/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.timersystemctl list-timers fstrim.timer --no-pagerecho none | sudo tee /sys/block/nvme0n1/queue/scheduler >/dev/nullecho 1024 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/nullecho 4096 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/nullecho 8192 | sudo tee /sys/block/nvme0n1/queue/read_ahead_kb >/dev/nullHDD 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/rotationalecho mq-deadline | sudo tee /sys/block/<device>/queue/scheduler >/dev/nullecho 8192 | sudo tee /sys/block/<device>/queue/read_ahead_kb >/dev/nullHDD 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/nullecho 8192 | sudo tee /sys/block/sda/queue/read_ahead_kb >/dev/nullecho 16384 | sudo tee /sys/block/sda/queue/read_ahead_kb >/dev/nullMeasured 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),
EOFRebuild 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 qbittorrentOn 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 qbittorrentsudo 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 --versiondpkg -l | grep -E 'qbittorrent|libtorrent'systemctl status qbittorrent@<username> --no-pagersysctl net.ipv4.tcp_congestion_control net.core.default_qdiscss -ltnp | grep qbittorrentMeasured 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.confsudo rm -f /etc/sysctl.d/90-canabrr-torrent-tuning.conf /etc/modules-load.d/canabrr-tcp-bbr.confsudo rm -rf /etc/systemd/system/qbittorrent@.service.dsudo sysctl --systemsudo systemctl daemon-reloadsudo rm -f /root/libtorrent-RC_1_2.patchsudo QBITTORRENT_VERSION=5.1.4 LIBTORRENT_VERSION=RC_1_2 box upgrade qbittorrentsudo 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.