Skip to content

qBit Tracker Safety — Setup

Setup

Overview

Four pieces need to be in place:

  1. The classifier script itself, on the host
  2. A wrapper shell script that runs inside the qBittorrent container (the AutoRun hook)
  3. Compose mounts so the container can see both scripts + credentials
  4. qBittorrent AutoRun preferences set to call the hook
  5. A cron fallback on the host

Prerequisites

Requirement Why
qBittorrent 5.0+ BEP 27 private field was added in the 5.0.0 WebUI API
Prowlarr API key accessible via env Script auto-syncs the private hostname blocklist from Prowlarr at runtime
qBittorrent WebUI credentials accessible via env Script needs to authenticate against the WebUI API
python3 inside the qBittorrent container The LinuxServer.io qBittorrent image ships with it by default

1. Compose Volume Mounts

The AutoRun hook runs inside the qBittorrent container, so the scripts and credentials must be mounted in. In your qBittorrent service definition:

qbittorrent:
  volumes:
    - $DOCKERDIR/appdata/qbittorrent:/config
    - /data/torrents:/data/torrents
    - $DOCKERDIR/scripts/system:/custom-scripts:ro    # AutoRun hook scripts
    - $DOCKERDIR/.env:/run/custom-scripts.env:ro      # creds for the hook

Don't nest the .env mount

Mount .env to /run/custom-scripts.env, not to /custom-scripts/.env. Bind-mounting a file inside another read-only bind mount is fragile and often fails on container recreate.

Recreate the container after editing:

docker compose -f docker-compose.yml -f apps/misc.yml up -d qbittorrent

2. The Classifier Script

Place on the host at $DOCKERDIR/scripts/system/trackerslist-inject.py. Key behaviors:

  • Loads environment from /srv/storage/docker/.env (host) or /run/custom-scripts.env (container)
  • Auto-resolves the qBittorrent + Prowlarr URLs via docker inspect when run from the host, or uses Docker DNS names (qbittorrent:8080, prowlarr:9696) when run inside the container
  • Accepts --dry-run, --tag-only, --hash <hash>, --list-hosts flags

Why two URL resolution modes

qBittorrent's WebUI\HostHeaderValidation rejects requests where the Host header doesn't match what it expects. From the host via a published port (localhost:9898), the host header is wrong. Resolving the container IP and hitting that directly sidesteps the issue.

Make it executable:

chmod +x /srv/storage/docker/scripts/system/trackerslist-inject.py

3. The AutoRun Hook Wrapper

qBittorrent's AutoRun feature expects a single command string. Since we need to set environment variables and log output, use a small wrapper shell script at $DOCKERDIR/scripts/system/trackerslist-inject-hook.sh:

#!/bin/bash
# qBit AutoRun hook — runs inside the qbittorrent container.
set -u
LOG=/config/trackerslist-hook.log
HASH="${1:-}"

{
  echo "[$(date -Iseconds)] hook fired hash=${HASH:0:12}"
  export QBIT_URL=http://localhost:8080
  /usr/bin/python3 /custom-scripts/trackerslist-inject.py --hash "$HASH"
  echo "[$(date -Iseconds)] hook done rc=$?"
} >> "$LOG" 2>&1

The script logs to /config/trackerslist-hook.log inside the container — visible from the host at $DOCKERDIR/appdata/qbittorrent/trackerslist-hook.log.

4. Wire qBittorrent AutoRun

qBittorrent has two separate AutoRun hooks. Both need to be enabled:

Preference key Fires when
autorun_on_torrent_added_enabled + autorun_on_torrent_added_program Torrent is added to the client
autorun_enabled + autorun_program Torrent finishes downloading

Easy mistake

The option in qBit's UI labeled "Run external program on torrent added" is the first key. The one labeled "Run external program on torrent finished" is the second. They're independent — enabling one does not enable the other. If you only enable one, newly-added magnet torrents may not get classified until they finish downloading.

Set both via the WebUI API:

# Authenticate once (adjust user/pass/IP)
curl -s -c /tmp/qbit.cookies -X POST "http://10.0.0.7:8080/api/v2/auth/login" \
  -H "Referer: http://10.0.0.7:8080" \
  --data-urlencode "username=Cognition0047" \
  --data-urlencode "password=YOUR_PASSWORD" > /dev/null

# Set both hooks
curl -s -b /tmp/qbit.cookies -X POST \
  "http://10.0.0.7:8080/api/v2/app/setPreferences" \
  --data-urlencode 'json={
    "autorun_on_torrent_added_enabled":true,
    "autorun_on_torrent_added_program":"/custom-scripts/trackerslist-inject-hook.sh \"%I\"",
    "autorun_enabled":true,
    "autorun_program":"/custom-scripts/trackerslist-inject-hook.sh \"%I\""
  }'

Or manually via the WebUI: Options → Downloads → Run external program on torrent added/finished, set both to:

/custom-scripts/trackerslist-inject-hook.sh "%I"

5. Cron Fallback

Add a daily (or hourly) full sweep on the host. This catches anything the hook missed and refreshes the trackerslist from GitHub:

# Add to crontab -e
0 4 * * * /srv/storage/docker/scripts/system/trackerslist-inject.py >> /tmp/trackerslist-inject.log 2>&1

For tighter reclassification windows (when you add private indexers to Prowlarr), run hourly instead:

0 * * * * /srv/storage/docker/scripts/system/trackerslist-inject.py >> /tmp/trackerslist-inject.log 2>&1

6. Initial Sweep

Before going live, do a dry-run to verify classification:

/srv/storage/docker/scripts/system/trackerslist-inject.py --dry-run

Expected output:

[info] blocklist: 13 private hosts (Prowlarr-synced + overrides)
[info] qbit: http://10.0.0.7:8080   prowlarr: http://10.0.0.162:9696
[info] 98 torrents to process
[info] trackerslist: 20 entries from https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt
[result] private: 70 (bep27=70 hostname=0)   public: 28

Check the counts match your expectations. If bep27=0 and all classifications came from hostname, something is wrong with the BEP 27 detection. If the private count is far lower than expected, check that your private tracker announce URLs actually match Prowlarr's indexer URLs (see the operation guide for debugging).

Once satisfied, flip it live:

/srv/storage/docker/scripts/system/trackerslist-inject.py --tag-only   # tag without injecting
# ...spot-check tags in qBit WebUI...
/srv/storage/docker/scripts/system/trackerslist-inject.py               # tag + inject
TL;DR

Mount the scripts dir + .env into qBittorrent, place the classifier + hook wrapper, enable both AutoRun preferences (on-added AND on-finished), add a cron fallback. Run --dry-run first to verify classification, then --tag-only to flip tags without injection, then the full run once you're satisfied.


Previous: ← How Classification Works Next: Daily Operation →