This is a minimalist Debian 12 (preferred) or Ubuntu Server seedbox setup that is oriented around fast and stable seeding, for both private and public trackers. It also covers optionally setting up qBittorent and Plex through a VPN.
It is bare-metal, not using any Dockers, so networking performance for 10gbit servers doesn’t suffer.
This guide has a lot of security measures / hardening done, so have patience when working through this guide.
Buying a server
Hetzner’s Auction House dedicated servers are preferred as it provides the best value; you get powerful hardware, a truly unlimited 1gbps line that is shared with nobody else, and good peering/routing.
For Hetzner, be sure to select an Intel CPU as it has an iGPU, which is useful for Plex, Emby, or Jellyfin; avoid Xeons, they have worse IPC which will impact libtorrent’s performance — the most critical part of qBittorrent, as it’s effectively an interface for libttorrent.
-
AMD CPUs are better value if you never use streaming services (Plex, Emby, or Jellyfin).
-
Select the FSN or NBG location for better peering, and use an Intel iNIC as it uses less CPU than alternative network cards, and can handle a high number of global connections via libtorrent.
If you’re paranoid about DDoS attacks, get an OVH unmetered from their website, and also check what Andy10gbit on Discord has to offer for OVH servers. Do note that OVH is significantly more expensive than Hetzner.
On Hetzner, format and install Debian 12 as RAID0 if applicable, as to not waste any storage and increase drive speeds. Also ensure /home
and /
are not separate partitions, meaning only /
is used, and make /
a XFS partition instead of ext4.
-
It’s preferred to backup externally, instead of relying on RAID1.
-
Serverpartdeals is the best company in the US selling manufacturer recertified drives. Their packaging is paranoid, shipping times are fast, and the warranty is good — although they won’t cover the shipping label’s cost.
-
Backing up this way will save a lot of money compared to paying Hetzner for backups, but will rely on you having fast download and upload speeds at home to do comfortably.
-
Login as the root
user after Debian’s installation is complete, or use sudo -i
to escalate to root privileges.
Install sudo:
# apt install sudo
If the current user is root, make an administrator account instead to avoid mistakes. By default it’ll be a user, unprivileged, but can escalate to root via using sudo
before a command:
# useradd -m -G sudo -s /bin/bash server
Set the passphrase to something moderately complex using a password manager like Bitwarden. I recommend at least 30 characters long, lower & upper case characters with numbers, and no symbols.
# passwd server
Login to 'server' and test if sudo works (it should output → root):
sudo whoami
The 'server' user is now going to be used instead of using 'root' directly.
💡
|
If you’re using a laptop as a server, set HandleLidSwitchExternalPower=ignore in /etc/systemd/login.conf .Then run: sudo systemctl restart systemd-logind
|
Remove Hetzner repositories, as it can make the OS lag behind in package updates, which is bad for security:
sudo rm /etc/apt/sources.list.d/hetzner-security-updates.list; sudo rm /etc/apt/apt.conf.d/99hetzner
Sudo edit /etc/apt/sources.list
, and remove all lines containing Hetzner.
💡
|
irqbalance balances interrupts across CPU cores to handle high load more efficiently, which can prevent low networking performance. AppArmor is to stop software from doing what they shouldn’t, hence better security. auditd & apparmor-notify is to allow viewing AppArmor logs, but also installing auditd allows AppArmor profiles to be created by you. Unattended upgrades is so you don’t accidentally forget to update packages, which would cause security issues. UFW is a firewall management tool for blocking off unused ports. Fail2ban is to make bruteforce password attacks on SSH and your web server (nginx) much more difficult. apt-transport-https is to allow apt to operate over HTTPS to prevent security flaws such as RCEs. |
sudo apt update && sudo apt install irqbalance apparmor apparmor-utils auditd apparmor-notify unattended-upgrades apt-listchanges ufw fail2ban python3-systemd apt-transport-https ansible
-
sudo apt install ubuntu-advantage-tools
-
You have to make an Ubuntu account to use Ubuntu Pro’s functionality:
sudo ua attach
For Debian: You can purchase KernelCare+ for $5.95 per server per month or $71 per server per year, it also hotpatches glibc and OpenSSL, which Ubuntu Pro doesn’t cover.
For Debian:
Sudo edit /etc/apt/sources.list
and change http://
to https://
, also enable the backports repository:
ℹ️
|
If bookworm-backports is not there, use this:deb https://deb.debian.org/debian bookworm-backports main contrib non-free-firmware Then run sudo apt update .
|
Run ssh-keygen
on your local computer/PC, use the default location unless you already have an SSH key there (in that case, put it elsewhere and remember it), then set the passphrase to something moderately complex using a password manager like Bitwarden. I recommend at least 30 characters long, lower & upper case characters with numbers, and no symbols.
-
Include
-i /DIRECTORY/TO/YOUR_FILE
if you put a custom location and/or name:
ssh-copy-id -p YOUR_SSH_PORT server@YOUR_SERVER_IP
-
Check on the server to see if your key was installed successfully:
cat ~/.ssh/authorized_keys
-
Login with only the SSH key for the 'server' user, and if it works, proceed with the next hardening step.
Install what we’ll use to automatically harden lots of settings relating to SQL, nginx, Linux, and SSH; note that for Debian 12 this will take a really long time, while on Ubuntu it’s fast:
sudo ansible-galaxy collection install -U devsec.hardening --force -vvv
Edit: ~/harden.yaml
; change the SSH server port to what you forwarded for your VPN if applicable:
- name: Do hardening via dev-sec's collection
hosts: localhost
roles:
- devsec.hardening.os_hardening
- devsec.hardening.ssh_hardening
vars:
- ssh_server_ports: ["55820"]
-
sudo ufw allow YOUR_SSH_PORT
-
sudo ufw allow https
Now we automatically harden:
sudo ansible-playbook ~/harden.yaml
Complete the firewall setup; this will kick you out of the SSH session, relogin in with the new SSH port.
sudo ufw default deny incoming; sudo ufw default allow outgoing; sudo ufw enable
Sudo edit /etc/default/grub
:
-
Remove
nomodeset
to allow the Intel iGPU to run, which is desirable for Plex, Emby, or Jellyfin.-
Also run:
sudo sed -i 's/^blacklist i915/#&/' /etc/modprobe.d/blacklist-hetzner.conf
-
-
Add
transparent_hugepage=never numa_balancing=disable
toGRUB_CMDLINE_LINUX_DEFAULT
, this aids in maintaining low networking latency. -
Add the kernel command line options from the Kernel Self Protection Project, and include the x86_64 options too. I would recommend using the "slow" options at first, to see if your server can handle it.
-
To make it easy (please check the KSPP link and compare):
hardened_usercopy=1 init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on page_alloc.shuffle=1 slab_nomerge pti=on nosmt iommu.passthrough=0 iommu.strict=1 mitigations=auto,nosmt vsyscall=none vdso32=0 cfi=kcfi
-
Generate the new boot configuration:
sudo grub-mkconfig -o /boot/grub/grub.cfg
Sudo edit /etc/sysctl.d/99-custom.conf
; note that these settings might be wasteful on 1gbps servers, but there shouldn’t be a perceivable negative impact from it:
# Don't save core dumps anywhere for better security, and less disk usage.
kernel.core_pattern = /dev/null
# Block processes with setuid from ignoring 'kernel.core_pattern'
fs.suid_dumpable = 0
# The fq (fair queueing) qdisc is recommended for BBR, instead of the default fq_codel
net.core.default_qdisc = fq
# Keep network throughput consistently high even with packet loss,
# at the cost of a little maximum upload burst
net.ipv4.tcp_congestion_control = bbr
# Use TCP Fast Open for both incoming and outgoing connections to reduce latency
net.ipv4.tcp_fastopen = 3
# Ensure MTU is valid to prevent stuck connections; very useful on misconfigured networks:
# https://blog.cloudflare.com/path-mtu-discovery-in-practice/
net.ipv4.tcp_mtu_probing = 1
# Allow TCP with buffers up to 16MB
net.core.rmem_default = 16777216
net.core.rmem_max = 16777216
net.core.wmem_default = 16777216
net.core.wmem_max = 16777216
net.core.optmem_max = 16777216
# Increase Linux autotuning TCP buffer limit to 64MB
net.ipv4.tcp_rmem = 4096 524288 67108864
net.ipv4.tcp_wmem = 4096 524288 67108864
# Don't swap to disk while the memory is not overloaded
vm.swappiness = 1
# Reduce TCP performance spikes by disabling timestamps
net.ipv4.tcp_timestamps = 0
# Done so TCP doesn't run out of memory
net.ipv4.tcp_mem = 3145728 4194304 6291456
# Protect against TCP TIME-WAIT assassination, which increases socket re-use
net.ipv4.tcp_rfc1337 = 1
# Allow 3/4 of available free memory in the receive buffer
net.ipv4.tcp_adv_win_scale = 2
# Allow ping to be ran under a normal user, fixing "Operation not permitted"
net.ipv4.ping_group_range = 0 1000
kernel.sched_autogroup_enabled = 0
net.core.netdev_budget = 209715
net.core.netdev_max_backlog = 3145728
net.core.somaxconn = 50000
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_orphan_retries = 2
net.ipv4.tcp_retries2 = 8
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_workaround_signed_windows = 1
vm.min_free_kbytes = 524288
vm.zone_reclaim_mode = 1
ℹ️
|
You can skip Swizzin installation if you already have it, for example, through hostingby.design’s Swizzin OS template. hostingby.design and Andy10gbit would in that case already have qBittorrent using libtorrent v1.2.x installed. If you want Plex though, run sudo box install plex
|
Install Swizzin, which are high-quality automation scripts to make administrating a seedbox easier; through which we install qBittorrent and optionally Plex
Use libtorrent v1.2.x instead of v2, as v2 has issues with disk performance / caching.
sudo -i
# export libtorrent_github_tag=RC_1_2
Retreive then run Swizzin:
# bash <(wget -qO - s5n.sh) && . ~/.bashrc
-
panel
-
nginx
-
qbittorrent → 4.3.9 or 4.6.5 depending on your preferences
-
plex (only if you’re streaming movies / TV shows)
See here for how to interact with Swizzin after its installation.
Exit the root user:
# exit
The following steps are required to make fail2ban work:
echo "sshd_backend = systemd" | sudo tee -a /etc/fail2ban/paths-debian.conf
Sudo edit /etc/fail2ban/fail2ban.local
:
[DEFAULT]
allowipv6 = auto
backend = systemd
banaction = ufw
banaction_allports = ufw
bantime = 2h
ignoreip = 127.0.0.1/8
logtarget = SYSTEMD-JOURNAL
maxretry = 5
Sudo edit /etc/fail2ban/jail.local
:
[sshd]
enabled = true
port = YOUR_SSH_PORT
[nginx-http-auth]
enabled = true
port = http,https
logpath = %(nginx_error_log)s
sudo systemctl restart fail2ban
Additional hardening via AppArmor:
sudo apt install -t bookworm-backports golang-go
-
If those two packages don’t exist, run:
echo 'deb http://deb.debian.org/debian bookworm-backports main contrib non-free' | sudo tee -a /etc/apt/sources.list
Optimize AppArmor for the loading of thousands of profiles:
echo 'write-cache' | sudo tee -a /etc/apparmor/parser.conf
echo 'Optimize=compress-fast' | sudo tee -a /etc/apparmor/parser.conf
Follow AppArmor.d’s official instructions on installing additional AppArmor profiles.
-
If there is a broken AppArmor profile, remove it, such as
sudo rm /etc/apparmor.d/home.tor-browser.firefox
.
Sudo edit /etc/apparmor.d/qbittorrent-nox
and add the following line (that contains @{HOME}):
Remove /storage/
if not applicable.
Now we can enforce AppArmor profiles for our web-facing applications:
sudo aa-enforce -d /etc/apparmor.d qbittorrent-nox php-fpm sshd
Restart the server to apply our GRUB and sysctl changes:
sudo systemctl reboot
Open the Swizzin panel, which should be on the root of your IP such as https://EXAMPLE_IP.
Click the Gear icon to go into the settings.
-
Default save path:
/home/YOUR_SWIZZIN_USER/torrents/qbittorrent
-
Use
/home/YOUR_SWIZZIN_USER/storage/torrents/qbittorrent
if on a hostingby.design server with both SSDs and HDDs.
-
-
Default Torrent Management Mode: Automatic
-
This is so you can download torrents based on category and have them be separated into their own sub-folder. For example: the category "mam" →
/home/YOUR_SWIZZIN_USER/torrents/qbittorrent/mam
.
-
-
Peer connection protocol: TCP
-
Use UPnP / NAT-PMP port forwarding from my router: ON
-
Uncheck all under Connections Limits!
-
sudo ufw allow PORT_FOR_INCOMING_CONNECTIONS
-
Encryption mode: Allow encryption
-
If using private trackers, uncheck all under Privacy, and NEVER enable anonymous mode.
-
Uncheck all under Torrent Queueing and Seeding Limits!
For 1gbit servers such as Hetzner
-
File pool size: 5000
-
Outstanding memory when checking torrents: 1024
-
512 if not using Hetzner / limited RAM such as 16GB.
-
-
Disk cache: -1
-
1024 to play it safe, or 0 if you experience memory leaks / 90-100% RAM usage.
-
-
Disk cache expiry: 60
-
Disk IO type: Default
-
Disk IO read mode: Enable OS Cache
-
Disk IO write mode: Enable OS Cache
-
Coalesce reads and writes: OFF
-
Use piece extent affinity: ON
-
Send upload piece suggestions: ON
-
Send buffer watermark: 5120
-
Send buffer low watermark: 512
-
Send buffer watermark factor: Between 200-250, adjust as needed
-
Outgoing connections per second: 50 (increase to 75 if racing on REDacted)
-
Socket backlog size: 1000
-
Type of service (ToS) for connections to peers: 128
-
μTP-TCP mixed mode algorithm: Prefer TCP
-
Support IDN: ON
-
Allow multiple connections from the same IP address: ON
-
Validate HTTPS tracker certificate: OFF
-
Server-side request forgery (SSRF) mitigation: ON
-
Upload slots behaviour: Fixed Slots
-
Upload choking algorithm: Fastest Upload
-
Always announce to all trackers in a tier: OFF
-
Always announce to all tiers: ON
-
Max concurrent HTTP announces: 50
-
Only use 75 if experiencing announce issues with a very high amount of torrents loaded.
-
-
Peer turnover disconnect percentage: 0
-
Peer turnover threshold percentage: 90
-
Peer turnover disconnect interval: 30
-
Max outstanding requests to a single peer: 500
For 10gbit servers
-
File pool size: 250000
-
Outstanding memory when checking torrents: 1024
-
512 on limited RAM such as 16GB.
-
-
Disk cache: -1
-
1024 to play it safe, or 0 if you experience memory leaks / 90-100% RAM usage.
-
-
Disk cache expiry: 60
-
Disk IO type: Default
-
Disk IO read mode: Enable OS Cache
-
Disk IO write mode: Enable OS Cache
-
Coalesce reads and writes: OFF
-
Use piece extent affinity: ON
-
Send upload piece suggestions: ON
-
Send buffer watermark: 20480
-
Send buffer low watermark: 2048
-
Send buffer watermark factor: 250
-
Outgoing connections per second: 50 (increase to 75 if racing on REDacted)
-
Socket backlog size: 1500
-
Type of service (ToS) for connections to peers: 128
-
μTP-TCP mixed mode algorithm: Prefer TCP
-
Support IDN: ON
-
Allow multiple connections from the same IP address: ON
-
Validate HTTPS tracker certificate: OFF
-
Server-side request forgery (SSRF) mitigation: ON
-
Upload slots behaviour: Fixed Slots
-
Upload choking algorithm: Fastest Upload
-
Always announce to all trackers in a tier: OFF
-
Always announce to all tiers: ON
-
Max concurrent HTTP announces: 50
-
Only use 75 if experiencing announce issues with a very high amount of torrents loaded.
-
-
Peer turnover disconnect percentage: 0
-
Peer turnover threshold percentage: 90
-
Peer turnover disconnect interval: 30
-
Max outstanding requests to a single peer: 500
This is to avoid complaints to Hetzner that would get your server shut down, which will always happen on public trackers, but are rare on private trackers.
|
This will slow down 10gbit servers to around 1.2gbit. |
Instructions
Here we’re going to use AirVPN (referral link, thank you if you use it); their servers are reliable, fast, and support port forwarding which is a requirement. I’ve personally used them since 2016, and struggled to find better VPNs, especially when needing port forwarding.
sudo ufw route allow in on wg0; sudo ufw allow 1637/udp
Open AirVPN’s website, go to "Client Area", then "VPN Devices → Manage". Here you assign a new device with whatever name you want; personally I’d name it "Hetzner".
Go back into "Client Area", then go to "Config Generator".
-
Choose "Linux" as the OS, click the slider for "Wireguard UDP 1637", then select your device. Now pick a server that has a 20000mbit/s (10gbps up and down) link; for Germany, their Netherlands servers are most suitable, while for Finland it would be Sweden.
-
At the bottom of the page, click "Generate".
-
Rename the generated VPN file to "wg0" ("wg0.conf" if you enabled file extensions in your OS).
Edit "wg0.conf":
-
Change the
MTU
to 1420. -
Remove the line containing
PersistentKeepalive
.
Install Wireguard on the server:
sudo apt install wireguard resolvconf
Sudo edit /opt/swizzin/swizzin.cfg
and add FORMS_LOGIN = False
ℹ️
|
This is required to login to the Swizzin panel when using alternative ports. |
Move "wg0.conf" to /etc/wireguard
; use an SFTP program such as FileZilla if you need to.
Sudo edit /etc/nginx/sites-enabled/default
-
Change the listen port from 443 to a port you have forwarded in AirVPN, note that the port and local port cannot differ on AirVPN’s website.
Using your Swizzin user, edit ~/.config/qBittorrent/qBittorrent.conf
:
-
Change
WebUI\LocalHostAuth
to false.-
It’s safe to bypass the localhost login requirement since Nginx protects this page already with a login.
-
Sudo edit /etc/ssh/sshd_config
, and change the Port to one you’ve port forwarded with AirVPN, note that again, the port and local port cannot differ on AirVPN’s website.
As root:
sudo systemctl restart ssh nginx panel qbittorrent@YOUR_SWIZZIN_USER
Enable the VPN on the server:
sudo wg-quick up wg0
Open the qBittorrent UI, likely https://example.airdns.org:12345
Click the Gear icon to go into the settings.
-
Network interface: wg0
Now for Plex, go to the URL — likely https://example.airdns.org:54321 (this must have its local port set to 32400), then click the wrench icon, go to Settings → Remote Access, and make sure it looks similar to the following:
Check your successful server logins occassionally with:
sudo last -w -F
View the AppArmor denials for 1 day:
sudo aa-notify -s 1 -v
Reload an AppArmor profile after changing it:
sudo aa-enforce THE_PROFILE
Monitor system resources live; run without sudo
to view the current user’s processes only:
sudo htop
There are three good options, two graphical, one command-line, depending on what you’re comfortable with.
-
On the server (example is of moving all files under
/home/EXAMPLE_USER/torrents/qbittorrent/
to IP 31.3.3.7 on SSH port 6969):
rsync --progress -atvz /home/EXAMPLE_USER/torrents/qbittorrent/* -e 'ssh -p 6969' [email protected]:/home/EXAMPLE_USER/torrents/qbittorrent
-
hostingby.design’s server templates.
-
ofnir & imabee’s advice on qBittorrent settings.
-
https://www.emqx.com/en/blog/emqx-performance-tuning-tcp-syn-queue-and-accept-queue
-
https://blog.cloudflare.com/optimizing-tcp-for-high-throughput-and-low-latency
-
https://learn.microsoft.com/en-us/azure/virtual-network/virtual-network-tcpip-performance-tuning
-
https://madaidans-insecurities.github.io/guides/linux-hardening.html