VPN-Shielded Torrent Stack (gluetun + qBittorrent)
Run qBittorrent inside a gluetun VPN container so all torrent traffic exits through a VPN with a kill-switch — plus port-forward sync for full download speed.
bash# docker-compose.yml — qBittorrent routed entirely through gluetun's VPN.
# qBittorrent shares gluetun's network namespace, so if the VPN drops,
# the torrent client loses all connectivity (kill-switch by design).
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add: [NET_ADMIN]
devices:
- /dev/net/tun:/dev/net/tun
ports:
- "8080:8080" # qBittorrent WebUI (published by gluetun)
environment:
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=YOUR_WIREGUARD_PRIVATE_KEY
- SERVER_COUNTRIES=Netherlands
- VPN_PORT_FORWARDING=on
- PORT_FORWARD_ONLY=on
# Let the client reach LAN services (e.g. your *arr apps)
- FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
network_mode: "service:gluetun" # <-- all traffic through the VPN
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- WEBUI_PORT=8080
volumes:
- /opt/qbittorrent/config:/config
- /srv/downloads:/downloads
depends_on:
- gluetun
restart: unless-stopped
What this does
Runs qBittorrent inside a gluetun VPN container so every byte of torrent traffic exits through your VPN — never your home IP. Because qBittorrent shares gluetun’s network namespace (network_mode: service:gluetun), if the VPN tunnel drops the client instantly loses all connectivity: a built-in kill-switch, not a bolt-on. The WebUI is still reachable on your LAN because gluetun publishes the port.
This is the download client that feeds a Plex library. Pair it with the ClamAV scan-and-purge playbook to keep the download folder clean.
Prerequisites
- Docker + Docker Compose (install guide)
- A VPN provider that supports WireGuard and port forwarding (port forwarding is what gets you full download speed — without it, incoming peer connections are blocked)
- Your WireGuard private key and preferred server country from the provider
- Adjust
192.168.1.0/24to your real LAN subnet, and paths to taste
Deploy
Save the script above as /opt/arr-vpn/docker-compose.yml, fill in your key, then:
sudo mkdir -p /opt/qbittorrent/config /srv/downloads
cd /opt/arr-vpn
sudo docker compose up -d
# Confirm traffic exits the VPN, NOT your home WAN:
sudo docker exec gluetun wget -qO- https://api.ipify.org ; echo
That IP must be your VPN provider’s, not your ISP’s. If it’s your home IP, stop — the tunnel isn’t up and nothing should be downloading yet.
The qBittorrent WebUI is at http://<host-ip>:8080. The linuxserver image prints a temporary admin password in its logs on first start:
sudo docker logs qbittorrent 2>&1 | grep -i password
Port-forward sync
Many VPN providers hand you a forwarded port that rotates. qBittorrent needs to listen on the current one for full speed. gluetun writes the active port to a file; sync it into qBittorrent on a schedule.
Save as /opt/arr-vpn/qbit-portsync.sh and cron it every 5 minutes:
#!/usr/bin/env bash
# Push gluetun's current forwarded port into qBittorrent.
set -euo pipefail
QBIT="http://127.0.0.1:8080"
USER="admin"
PASS="YOUR_QBIT_PASSWORD"
PORT=$(sudo docker exec gluetun cat /tmp/gluetun/forwarded_port)
# qBittorrent 5.x returns HTTP 204 (empty body) on a successful login
COOKIE=$(curl -s -i --data "username=$USER&password=$PASS" \
"$QBIT/api/v2/auth/login" | grep -i '^set-cookie' | sed 's/.*SID=\([^;]*\).*/\1/')
curl -s --cookie "SID=$COOKIE" \
--data "json={\"listen_port\":$PORT}" \
"$QBIT/api/v2/app/setPreferences"
echo "synced listen_port=$PORT"
# crontab -e
*/5 * * * * /opt/arr-vpn/qbit-portsync.sh >> /var/log/qbit-portsync.log 2>&1
Notes
- Verify the kill-switch: stop gluetun (
docker stop gluetun) and confirm qBittorrent loses network. It shares the namespace, so it must go dark. FIREWALL_OUTBOUND_SUBNETSis what lets qBittorrent (and anything else in the namespace) still reach LAN services like Radarr/Sonarr while everything else is forced through the VPN. Set it to your real subnet.- Route Prowlarr behind the same VPN for full metadata shielding by adding
network_mode: service:gluetunto it too, and publishing its port on the gluetun service. Note that some CloudFlare-protected indexers block datacenter/VPN exit IPs — prefer indexers that work from a VPN exit. - Don’t recreate the stack while downloads are active unless you have to — recreating gluetun restarts qBittorrent and pauses in-flight torrents.
- qBittorrent 5.x login quirk: a successful login returns HTTP 204 with an empty body (not the string “Ok.”), and repeated failed logins trigger a temporary per-IP auth ban — restart the container to clear it.
- Never commit your real WireGuard key. Keep it in the compose file on the host only, or reference it from an
.envfile that’s outside version control.