Homelab Haven

Self-Hosted Media Stack, Part 3: qBittorrent Behind a VPN, Plus SABnzbd

July 15, 20265 min read

Part 3 of Media Stack from Scratch. Part 2 left Sonarr and Radarr with a wanted list and no way to act on it — this part gives them muscle. You'll want the folder layout from Part 1 in place: everything below assumes /Media with downloads/torrents/ and downloads/usenet/ inside it.

The download clients are the only part of the stack that talks to the outside world, and the torrent client goes behind a VPN in a way that fails closed: if the tunnel drops, downloads stop. Not "might leak for a few seconds" — stop.

Summary

  • Pick one, or run both — qBittorrent and SABnzbd are two independent sections below, not a package deal. Skip to whichever you actually want.
  • One container image handles qBittorrent and the VPN: binhex/arch-qbittorrentvpn. The tunnel lives inside the container, so nothing else on the host is routed through it.
  • If the VPN is down, the container has no route out. That's the fail-closed part.
  • SABnzbd needs no VPN — Usenet is TLS to a provider you pay, not a swarm of peers.
  • Both clients download into /Media/downloads/... so imports are hardlinks, not copies.

Torrents, Usenet, or both?

Sonarr and Radarr don't care how a file arrives — they just need a download client to hand releases to. That means this choice is genuinely yours, not a stack requirement:

  • Only qBittorrent — skip straight to that section, then to "Introduce them to Sonarr and Radarr" and connect qBittorrent alone. Free, but content availability depends entirely on what indexers turn up and how well-seeded it is.
  • Only SABnzbd — skip to that section instead. Usenet is a paid provider subscription (plus usually a separate indexer subscription), but retention is measured in years and speed doesn't depend on other users seeding.
  • Both, which is what I run — the *arrs try Usenet and torrents as separate options per release and use whichever scores higher, so you get Usenet's reliability with torrents filling the gaps. This is the setup the rest of this part walks through.

Whichever you pick, the folder layout and hardlink logic from Part 1 apply identically — nothing below changes based on this choice except which section you read.

qBittorrent + VPN in one container

services:
  qbittorrent:
    # Pin the version. VPN images break on 'latest' more than any other kind of
    # container - when networking inside the image changes, the symptom is a client
    # that starts but never connects, which looks exactly like a VPN account problem.
    image: binhex/arch-qbittorrentvpn:5.2.2-1-01
    container_name: qbittorrent
    restart: unless-stopped
    environment:
      - VPN_ENABLED=yes
      - VPN_PROV=custom          # 'custom' = bring your own .ovpn file
      - VPN_CLIENT=openvpn
      - LAN_NETWORK=192.168.1.0/24   # YOUR local subnet - see below
      - NAME_SERVERS=1.1.1.1,1.0.0.1
      - WEBUI_PORT=8085
      - PUID=1001
      - PGID=100
      - TZ=Europe/Amsterdam
    volumes:
      - /path/to/appdata/qbittorrent:/config
      - /path/to/Media:/Media
    ports:
      - "8085:8085"
    devices:
      - /dev/net/tun             # the container builds the tunnel itself
    cap_add:
      - NET_ADMIN                # and needs this to manage its own routing

Drop your provider's .ovpn file (and credentials file, if separate) into /path/to/appdata/qbittorrent/openvpn/ before first start. The container picks it up, builds the tunnel, and only then starts qBittorrent.

📄 Using Surfshark? Here's my actual compose file, masked — the real thing from my NAS with private values replaced, including where the Surfshark .ovpn and service credentials go. Works the same for any provider that hands you an OpenVPN config.

The two settings everyone gets wrong:

  1. LAN_NETWORK must match your actual LAN subnet. This is the route exception that lets you reach the web UI while everything else goes through the tunnel. Get it wrong and the UI is unreachable — which looks like the container is broken when it's actually doing its job.
  2. The web UI port must match WEBUI_PORT on both sides of the mapping. The VPN image rewrites its firewall rules around WEBUI_PORT; mapping a different host port to it is fine, but changing the UI port only in qBittorrent's settings and not in the environment locks you out on next restart.

Verify the tunnel before moving on — from inside the container, the exit IP must be your VPN provider's, not your home IP:

docker exec qbittorrent curl -s ifconfig.me

In qBittorrent's settings, set the default save path to /Media/downloads/torrents/.

SABnzbd

Usenet needs no tunnel, so this one is plain:

  sabnzbd:
    image: linuxserver/sabnzbd:4.5.5
    container_name: sabnzbd
    restart: unless-stopped
    environment:
      - PUID=1001
      - PGID=100
      - TZ=Europe/Amsterdam
    volumes:
      - /path/to/appdata/sabnzbd:/config
      - /path/to/Media:/Media
    ports:
      - "8080:8080"

First-run wizard: enter your provider's server, port 563 (TLS), and your credentials. Then in Folders, set temporary download to /Media/downloads/usenet/incomplete and completed to /Media/downloads/usenet/. Same reasoning as the torrent side: keep it inside the /Media mount so the *arrs' imports are hardlinks.

Introduce them to Sonarr and Radarr

This is the payoff of doing Part 2 first — the *arrs are waiting, so the wiring is one settings page, done for whichever client(s) you set up:

In both Sonarr and Radarr → Settings → Download Clients → Add:

  • qBittorrent (if you set it up) — host qbittorrent, port 8085, plus the web UI username and password. Container names resolve because everything shares the compose network.
  • SABnzbd (if you set it up) — host sabnzbd, port 8080, plus its API key (SABnzbd → Config → General).

Adding both is what lets the *arrs pick whichever scores higher per release, rather than being locked to one source. Hit Test on each — green checkmarks or fix it now, while the moving parts are few. From this moment on you stop opening the downloader UIs: the *arrs drive them, and the *arr queue is where progress lives.

Checkpoint

Whichever client(s) you set up:

  • qBittorrent reachable on its web UI, exit IP = VPN provider's IP, a manually-added torrent landing in /Media/downloads/torrents/
  • SABnzbd connected to your provider, test download landing in /Media/downloads/usenet/
  • that client green under Download Clients in Sonarr and Radarr

The wanted list from Part 2 still sits idle — the *arrs can now fetch, but they still can't search. Eyes next.

Next: Part 4 — Prowlarr, one place for all your indexers. Previous: Part 2 — Sonarr and Radarr.

If this saved you some time, a coffee keeps the lights on and the posts coming.

☕ Buy me a coffee