Files
hermes-config/skills/self-hosting/self-hosted-torrent-client/SKILL.md
T
2026-07-12 10:17:17 -04:00

7.7 KiB

name, description, version, platforms, metadata
name description version platforms metadata
self-hosted-torrent-client Deploy and troubleshoot a Dockerized torrent client (qBittorrent) behind a VPN (Gluetun) with private tracker support, including dynamic IP registration via Mousehole for trackers like MyAnonaMouse (MAM). 1.0.0
linux
hermes
tags category
torrent
seedbox
vpn
gluetun
qbittorrent
private-tracker
mam
self-hosting

Self-Hosted Torrent Client (qBittorrent + Gluetun + Private Trackers)

Deploy and maintain a Dockerized qBittorrent behind a VPN tunnel (Gluetun) with support for private trackers that require IP registration (MAM, etc.).

Architecture

qBittorrent ──┤                  │
              │  network_mode:   │
Mousehole  ───┤  service:gluetun │── tun0 (VPN) ── Internet
              │                  │
Gluetun ──────┤  VPN client      │── enp2s0 (host network)

All three containers share Gluetun's network namespace. Only Gluetun declares port mappings — qBittorrent and Mousehole inherit them.

Quickstart

1. Docker Compose

Use a single compose file with three services:

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
      - "5010:5010"               # Mousehole WebUI (use 0.0.0.0:5010 if accessing from LAN)
    environment:
      VPN_SERVICE_PROVIDER: "private internet access"
      VPN_TYPE: "openvpn"
      OPENVPN_PROTOCOL: "tcp"
      OPENVPN_USER: "<your-username>"
      OPENVPN_PASSWORD: "<your-password>"
      SERVER_HOSTNAMES: "nl-amsterdam.privacy.network"
      VPN_PORT_FORWARDING: "on"
      PORT_FORWARD_ONLY: "true"
      FIREWALL_VPN_INPUT_PORTS: "8080,5010"
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    network_mode: "service:gluetun"
    depends_on:
      gluetun:
        condition: service_healthy
    volumes:
      - ./qbittorrent:/config
      - /path/to/downloads:/downloads
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
      - WEBUI_PORT=8080
    restart: unless-stopped

  mousehole:
    image: tmmrtn/mousehole:latest
    container_name: mousehole
    network_mode: "service:gluetun"
    depends_on:
      gluetun:
        condition: service_healthy
    environment:
      TZ: America/Chicago
      MOUSEHOLE_AUTH_PASSWORD: <strong-password>
      MOUSEHOLE_ALLOWED_HOSTS: localhost,127.0.0.1,[::1],192.168.50.98
    volumes:
      - mousehole:/var/lib/mousehole
    restart: unless-stopped

volumes:
  mousehole:

2. Set up qBittorrent

  • Access WebUI at http://<host-ip>:8080
  • Go to Settings → Connection
  • Set Listening Port to match Gluetun's forwarded port
  • Disable UPnP/NAT-PMP
  • The port is stored in Session\Port in qBittorrent.conf
  • Verify the forwarded port from Gluetun: docker exec gluetun cat /tmp/gluetun/forwarded_port

3. Set up Mousehole (for private trackers needing dynamic IP)

  • Access Mousehole WebUI at http://<host-ip>:5010
  • Log in with the MOUSEHOLE_AUTH_PASSWORD
  • On MAM, go to Preferences → Security and create a session:
    • IP: Use the IP Mousehole shows you
    • IP vs ASN locked: ASN
    • Allow Session to set Dynamic Seedbox: Yes
    • Label: mousehole
  • Copy the cookie MAM gives you
  • Paste it into Mousehole and click Set

Common Private Tracker Issues

Symptom Likely Cause Fix
"Unrecognized host/PassKey" Browser IP ≠ VPN IP (private tracker mismatch) Set up Mousehole with MAM Dynamic Seedbox API
"Non-Whitelisted client or version" Client not on tracker's allowed list Check tracker's allowed clients page; may need to downgrade/pin a version
Stalled torrent, no peers Tracker not being contacted or port not reachable Verify QBT port matches forwarded port, check tracker reachability: curl -s -o /dev/null -w "%{http_code}" "<tracker-announce-url>"
Port mismatch Gluetun forwarded port ≠ QBT listening port After Gluetun restart, check docker exec gluetun cat /tmp/gluetun/forwarded_port and update QBT config

Pitfalls & Gotchas

qBittorrent WebUI: IPv6-Only Binding on linuxserver.io Images

Symptom: qBittorrent logs "WebUI: Now listening on IP: *, port: 8080" but the WebUI is unreachable from the Docker host via IPv4. Connection gets "Connection refused" on the container's Docker IP, even though qBittorrent is running and healthy.

Root cause: On linuxserver.io qBittorrent images (v5.2.x), the start script changes WebUI\Address=* to WebUI\Address=localhost before launch. qBittorrent 5.2.x then binds the WebUI only to IPv6 ([::]:8080). The Docker proxy connects via IPv4 (the container's Docker IP), which fails because nothing is listening on IPv4:8080.

Diagnosis:

docker exec qbittorrent sh -c 'cat /proc/net/tcp /proc/net/tcp6' | grep -i "1F90"
  • /proc/net/tcp (IPv4) — port 8080 (0x1F90) ABSENT
  • /proc/net/tcp6 (IPv6) — port 8080 PRESENT on [::]:1F90

Fix: Set WebUI\Address=0.0.0.0 in qBittorrent.conf (instead of *). The start script's localhost override only triggers on empty or * values. Then restart qBittorrent.

Port Binding

  • 127.0.0.1:5010:5010 binds to localhost ONLY — not accessible from LAN or other containers
  • Use 0.0.0.0:5010:5010 or just 5010:5010 for LAN access
  • Default docker compose restart does NOT pick up port binding changes — use docker compose up -d --force-recreate <service> instead

Mousehole Host Header

  • Default MOUSEHOLE_ALLOWED_HOSTS only allows localhost, 127.0.0.1, [::1]
  • Add your LAN IP or hostname when accessing from another machine

Gluetun Recreates

  • When Gluetun is recreated, qBittorrent may auto-detach if using network_mode: service:gluetun
  • Docker Compose handles this correctly via container name, not container ID
  • The PIA VPN IP WILL change on restart — Mousehole handles re-registration

Gluetun VPN IP Changes

  • PIA and other VPN providers assign different IPs on reconnect
  • This perfectly normal — Mousehole re-registers the new IP with the tracker
  • Check current VPN IP: docker exec gluetun cat /tmp/gluetun/ip

Stale Containers

  • Old, unhealthy gluetun instances (e.g., vigorous_lewin) can sit around after config changes
  • Check for them with docker ps -a and clean up: docker rm <container>

Port Forwarding Verification

  1. Check forwarded port: docker exec gluetun cat /tmp/gluetun/forwarded_port
  2. Check what port QBT is listening on from its logs or config
  3. Test port is open on VPN interface: docker exec qbittorrent python3 -c "import socket; s=socket.socket(); s.settimeout(3); print(s.connect_ex(('10.26.x.x', 36590)))" (use actual tun0 IP from QBT logs)

Tracker Connectivity Test

  • From inside the container: docker exec qbittorrent curl -s -o /dev/null -w "%{http_code} (%{time_total}s)" --connect-timeout 15 "<tracker-url>"
  • A 404 on the announce URL is expected (missing required params) — means the tracker is reachable
  • A 200 with "failure reason" means tracker is rejecting the announce specifically — check the reason text

Verification

After setup, confirm:

  1. Gluetun is healthy: docker ps --filter name=gluetun --format '{{.Status}}'
  2. Port forwarding is active: docker exec gluetun cat /tmp/gluetun/forwarded_port
  3. qBittorrent is using the forwarded port from its config
  4. Mousehole shows OK status after cookie is set
  5. Add a public tracker torrent to qBittorrent to verify general connectivity
  6. Add the private tracker torrent and check tracker status via MAM's tracker detail page