2026-05-16 Media Stack Download Pipeline Recovery
What I set out to do
Noticed a few stuck downloads in qBittorrent. Wanted to understand why and clear them. Turned into a much wider session that uncovered a force-resume cascade breaking auto-cleanup, dead public-tracker swarms for older content, and ultimately the root cause behind all of it: port 6881 was never reachable from WAN, so every torrent that had to rely on inbound connections was crippled.
What I actually did
Worked through the stack live via API. No commits — everything was runtime state changes against Sonarr / Prowlarr / qBittorrent / the router. Two follow-up project notes capture what needs to be encoded back into source-of-truth.
-
Initial diagnosis: 6 stuck Industry S01 torrents + 1 stuck AoT S04E01. All
forcedDL, 0 KB/s, 0-25% progress. All sourced from1337xre-uploads of NTb releases, all in dead swarms (trackers reported 5-7 seeds, qBittorrent connected to 1-3, none serving). Bulk-deleted + blocklisted via Sonarr’s/api/v3/queue/bulk. Sonarr’sautoRedownloadFailedimmediately re-grabbed the same releases under slightly different filenames — blocklist matches title strings, not release groups or indexers. Second pass usedskipRedownload=trueto stop the loop. -
MHA S00E23 importPending. Torrent display name said “S00E23 More” but VARYG had renamed the file inside the torrent to
S08E147(their absolute-numbering scheme, but MHA only has 7 seasons + specials). Sonarr’s filesystem parser sawS08E147, found no such episode, rejected as “Invalid season or episode.” UsedPOST /api/v3/command {name:ManualImport}to bind the file to episodeId 102. Replaced the existing ToonsHub/NF rip with the VARYG/CR dual-audio release (CF score 306 vs whatever ToonsHub was — Anime Dual Audio kicker). -
Deprioritized 1337x in Prowlarr via API. PUT each indexer with new priority: Nyaa.si=15, EZTV=20, 1337x=40. Triggered
POST /api/v1/command {name:ApplicationIndexerSync,forceSync:true}to push to Sonarr/Radarr. Confirmed not persistent —scripts/setup-prowlarr.sh:92,112hardcodes.priority = 25on the JSON sent at indexer creation, and the script’sadd_indexershort-circuits when the indexer already exists (scripts/setup-prowlarr.sh:67-69). The early-return is technically a bug per AGENTS.md (“define the desired state and always apply it”) but works in our favor here — API changes survivedocker compose up. They don’t survivedown -vor a future fix to that early-return. Captured in Persist Prowlarr Indexer Priorities in setup-prowlarr.sh for a separate worktree. -
Plex was missing several Industry S01 episodes I didn’t realize I’d broken. Cleanup nuked 6, only 3 were imported beforehand (E05, E06, E08). Manually grabbed CAKES (E01, E04, E07) and GGWP (E02, E03) via
POST /api/v3/releaseafter removing the matching blocklist entries. All but E03 grabbed cleanly. E03 GGWP had a particularly dead swarm — single reported seed across all working trackers. -
18 torrents stuck in
forcedUPcluttering the list. Caused by manual Force Resume clicks (user confirmed).force_start: Trueoverridesmax_ratio=0+ShareLimitAction=Stop, so Sonarr’sremoveCompletedDownloadsnever sees the pause-on-complete signal that gates cleanup. Wrote qBittorrent Force Resume Bypasses Share Limits as an atomic note. Fixed withPOST /api/v2/torrents/setForceStart value=falseon all 18 hashes. Torrents transitionedforcedUP → stoppedUP, Sonarr’sRefreshMonitoredDownloadsswept them within seconds. List shrank 20 → 2. -
AoT S04E01 was “queue-blocked” by a dead Funimation metaDL torrent. Every alternative was rejected with “Release in queue already meets cutoff: HDTV-1080p v1” because Sonarr’s release-decision logic treats a queued-but-stuck torrent as “we already have a candidate.” Removed the Funimation queue item with
blocklist=false(might be viable later, just not now), re-searched, grabbed[SubsPlease] Shingeki no Kyojin (The Final Season) - 60 (1080p)from Nyaa.si. Same pattern repeated for Industry S01E03 — the GGWP queue blocker prevented re-grabbing the NTb release we’d unblocklisted. -
Bumped queue caps from 3 to 20. User asked.
max_active_downloads,max_active_torrents,max_active_uploadsall set to 20. Observed no speed gain — adding 13 more peer connections to AoT S04E17 took it from 41 KB/s to 48 KB/s. Confirmed the limit was per-torrent swarm health, not slot count. -
Libtorrent thread tuning.
async_io_threads=10 → 32,hashing_threads=1 → 8,memory_working_set_limit=512 → 2048,file_pool_size=100 → 500. Container has 8 cores, host has 16. Tuned to ~4× container cores per libtorrent’s recommendation. Helps recheck/import throughput; won’t help network-bound speeds. -
Found the actual root cause. Used
canyouseeme.orgPOST-form probe from the host (the only way to actually test inbound from WAN — qBittorrent’sconnection_status: connectedis a self-report based on tracker chatter, not an external probe). Result: port 6881 NOT reachable from outside. This explained the entire 6-month pattern of “trackers report N seeds, qBittorrent connects to 2.” Without inbound, qBittorrent can only initiate outbound; every peer behind their own NAT (residential, mobile, etc.) is unreachable. -
macOS firewall was already disabled (
socketfilterfw --getglobalstate= 0). Mac was listening on*:6881(Docker port mapping intact). Router gateway at10.0.0.1— Rogers Ignite gateway, identified by theServer: Xfinity Broadband Router Serverheader. The 24ms ping to the LAN gateway is bizarre (typical LAN is <3ms) — suggests Wi-Fi mesh or bufferbloat on this Mac specifically; not relevant to the port issue but worth noting. -
SSDP M-SEARCH from the host found the router’s UPnP IGD at
http://10.0.0.1:49152/IGDdevicedesc_brlan0.xml. The Rogers gateway runsLinux/5.4.201-prod-23.2-231009, UPnP/1.0. qBittorrent hasupnp: Truein preferences but its UPnP requests never reach the router — Docker bridge networks don’t forward SSDP multicast (239.255.255.250), so qBittorrent can’t discover the IGD from inside the container. This is the structural reason port-forward never auto-configured itself despite both ends supporting UPnP. -
Added the port mapping via SOAP from the host. Parsed the IGD description, found the WANIPConnection control URL, POSTed
AddPortMappingfor both TCP and UDP. Both returned HTTP 200. Verified withGetGenericPortMappingEntryenumeration — entries [1] (TCP) and [2] (UDP) for 6881 → 10.0.0.46:6881, both enabled. Re-tested withcanyouseeme.org: “Success: I can see your service on 99.234.104.89 on port (6881).” -
Effect was immediate. AoT S04E17 jumped from
conn=2toconn=17after a tracker reannounce. AoT S04E01 (SubsPlease - 60) went fromqueuedDL0 KB/s to 3.9 MB/s downloading. After grabbing the previously-stalled releases, Industry S01E03 NTb (unblocklisted, then re-grabbed) connected to 13 peers immediately and started downloading at ~60 KB/s — a release that had been completely dead during the first cleanup pass. -
End state: AoT S04 30/30, Industry S01 7/8 (E03 in flight), MHA S00E23 upgraded to dual-audio, qBittorrent list down to ≤2 active torrents at any time, port forward live on the router, Prowlarr priorities active.
What was striking
-
The “queue blocker” pattern repeated three times. Each time, Sonarr was unable to grab better alternatives because its release-decision logic treats whatever’s in the queue (even a 0% stuck torrent) as “we already have a candidate of this quality.” The first stuck Industry NTb torrents blocked Sonarr’s auto-redownload from finding CAKES/GGWP. The Funimation S04E01 metaDL blocked grabbing SubsPlease. The GGWP S01E03 stall blocked re-grabbing NTb. Removing the queue item is the precondition for any alternative grab to succeed, not just a cleanup step. Worth noting somewhere durable that “stuck queue item ≠ inert; it actively suppresses alternatives.”
-
Force Resume is a footgun in a managed pipeline. It’s a UI affordance for “just keep this going no matter what,” but in a Sonarr+qBittorrent setup the entire cleanup automation hangs on torrents reaching a paused/stopped state. Clicking Force Resume on a few torrents to “fix” a slow download silently disables
removeCompletedDownloadsfor those torrents forever. The 18 zombie seeders in the list were the receipts. Captured in qBittorrent Force Resume Bypasses Share Limits. -
The fact that
connection_status: connectedis misleading. qBittorrent shows this when it can ping a tracker — not when it’s actually reachable from WAN. The only way to actually know is to probe from outside the LAN. Documented this so future-me doesn’t trust the self-report. Months of “slow downloads” probably traced to no-inbound-port; the symptom was masked by qBittorrent claiming it was fine. -
UPnP-in-Docker doesn’t work for the reason I expected and didn’t expect. I assumed UPnP was off on the router. It wasn’t — the router happily responds to SSDP from the host. The blocker is that Docker’s bridge network silently drops multicast (
239.255.255.250SSDP discovery) before it can reach the LAN. qBittorrent’s UPnP module sends discovery requests that never get a reply. This is a fully solved problem (usenetwork_mode: host, orhost.docker.internal, or run a UPnP relay) but no one in the linuxserver/qbittorrent docs mentions it. The path of least resistance is what I did today: bypass the container’s UPnP, add the mapping from the host via SOAP. Captured in Persist qBittorrent UPnP Port Mapping in Media Stack for the work that needs to land in main. -
The seedbox
37.48.71.178from Worldstream (NL) was the actual hero for the Industry CAKES/GGWP grabs. Same IP appeared on multiple torrents at 2-3 MB/s each. Without that one box in the swarm, even the “healthy” public-tracker releases would have been slow. Public-tracker BitTorrent for older content is essentially “are any commercial seedboxes still serving this release” — there’s almost no organic residential seeding anymore for 2020-era TV.
Top 3 tomorrow
-
Spin a worktree off
mainfor Persist Prowlarr Indexer Priorities in setup-prowlarr.sh — add apriorityargument toadd_indexer(), set the three overrides (Nyaa.si=15, EZTV=20, 1337x=40), and fix the early-return bug atscripts/setup-prowlarr.sh:67-69while there. Local test viadocker compose up -d orchestrator. -
Spin a worktree for Persist qBittorrent UPnP Port Mapping in Media Stack. Decide between the three approaches (network_mode: host vs UPnP renewal cron vs document manual setup) and implement. The UPnP mapping I added today survives until the router reboots; without a renewal mechanism we lose it on Rogers’ ~quarterly firmware push.
-
Make a real decision about Usenet. Newshosting + NZBGeek is ~$15/mo total for the kind of reliability that today’s debugging session was symptomatic of. Public trackers for 2020-era TV are a losing fight no matter how much I tune Prowlarr.
Related
- Persist Prowlarr Indexer Priorities in setup-prowlarr.sh — earlier project note from this session
- Persist qBittorrent UPnP Port Mapping in Media Stack — sibling project note for today
- qBittorrent Force Resume Bypasses Share Limits — atomic note from this session
- 2026-05-16 SigNoz Dockerstats and OpAMP Investigation — same-day journal on a different stack