Is qBittorrent Down? Real-Time Status & Outage Checker
Is qBittorrent Down? Real-Time Status & Outage Checker
qBittorrent is an open-source BitTorrent client with 28,000+ GitHub stars and the most popular self-hosted download client for home media servers. It offers a full Web API (qBittorrent-nox for headless operation), RSS automation, sequential downloading, IP binding, and search plugins. It integrates with Sonarr, Radarr, Lidarr, and other *arr apps via its Web API, making it the standard BitTorrent backend for automated media pipelines. Available on Linux, macOS, Windows, and as a Docker container. When qBittorrent goes down or loses authentication, every *arr app loses its download client, queued grabs stall, and your media pipeline stops completely.
qBittorrent exposes its Web API on port 8080 by default. Monitoring authentication, transfer state, and active torrent health is the fastest way to catch failures before they cascade into hours of missed downloads.
Quick Status Check
#!/bin/bash
# qBittorrent health check
# Usage: QB_USERNAME=admin QB_PASSWORD=adminadmin ./qbittorrent-check.sh
QB_HOST="${QB_HOST:-http://localhost:8080}"
QB_USERNAME="${QB_USERNAME:-admin}"
QB_PASSWORD="${QB_PASSWORD:-adminadmin}"
PASS=0
FAIL=0
echo "=== qBittorrent Health Check ==="
echo "Host: $QB_HOST"
echo ""
# 1. Check process is running
if pgrep -x "qbittorrent-nox" > /dev/null 2>&1 || pgrep -x "qbittorrent" > /dev/null 2>&1; then
echo "[OK] qBittorrent process is running"
PASS=$((PASS+1))
else
echo "[FAIL] qBittorrent process not found"
FAIL=$((FAIL+1))
fi
# 2. Check port 8080 is open
if nc -z localhost 8080 2>/dev/null; then
echo "[OK] Port 8080 is open"
PASS=$((PASS+1))
else
echo "[FAIL] Port 8080 is not reachable"
FAIL=$((FAIL+1))
fi
# 3. Check app version (unauthenticated)
VERSION=$(curl -sf --max-time 10 "$QB_HOST/api/v2/app/version")
if [ $? -eq 0 ]; then
echo "[OK] API responding — qBittorrent v$VERSION"
PASS=$((PASS+1))
else
echo "[FAIL] /api/v2/app/version did not respond"
FAIL=$((FAIL+1))
fi
# 4. Authenticate and get SID cookie
SID=$(curl -sf --max-time 10 -c - \
--data "username=$QB_USERNAME&password=$QB_PASSWORD" \
"$QB_HOST/api/v2/auth/login" | grep SID | awk '{print $NF}')
if [ -n "$SID" ]; then
echo "[OK] Authentication succeeded — SID obtained"
PASS=$((PASS+1))
else
echo "[FAIL] Authentication failed — check username and password"
FAIL=$((FAIL+1))
fi
# 5. Check transfer info (requires auth)
if [ -n "$SID" ]; then
TRANSFER=$(curl -sf --max-time 10 \
-b "SID=$SID" \
"$QB_HOST/api/v2/transfer/info")
if [ $? -eq 0 ]; then
DL_SPEED=$(echo "$TRANSFER" | grep -o '"dl_info_speed":[0-9]*' | cut -d: -f2)
echo "[OK] Transfer info accessible — DL speed: ${DL_SPEED:-0} B/s"
PASS=$((PASS+1))
else
echo "[FAIL] /api/v2/transfer/info did not respond"
FAIL=$((FAIL+1))
fi
fi
echo ""
echo "=== Result: $PASS passed, $FAIL failed ==="
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
Python Health Check
#!/usr/bin/env python3
"""
qBittorrent health check
Authenticates via Web API, checks version, transfer info, DHT status, torrent states,
and global stats.
"""
import os
import sys
import json
import urllib.request
import urllib.error
import urllib.parse
import http.cookiejar
QB_HOST = os.environ.get("QB_HOST", "http://localhost:8080")
QB_USERNAME = os.environ.get("QB_USERNAME", "admin")
QB_PASSWORD = os.environ.get("QB_PASSWORD", "adminadmin")
results = []
failures = 0
cookie_jar = http.cookiejar.CookieJar()
cookie_handler = urllib.request.HTTPCookieProcessor(cookie_jar)
opener = urllib.request.build_opener(cookie_handler)
def check(label, ok, detail=""):
global failures
status = "OK " if ok else "FAIL"
if not ok:
failures += 1
msg = f"[{status}] {label}"
if detail:
msg += f" — {detail}"
results.append(msg)
print(msg)
def api_get(path, timeout=10):
url = f"{QB_HOST}{path}"
req = urllib.request.Request(url)
try:
with opener.open(req, timeout=timeout) as resp:
return resp.read().decode()
except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP {e.code} from {path}")
except Exception as e:
raise RuntimeError(f"Request failed: {e}")
def api_get_json(path, timeout=10):
return json.loads(api_get(path, timeout))
def api_post(path, data, timeout=10):
url = f"{QB_HOST}{path}"
body = urllib.parse.urlencode(data).encode()
req = urllib.request.Request(url, data=body,
headers={"Content-Type": "application/x-www-form-urlencoded"})
try:
with opener.open(req, timeout=timeout) as resp:
return resp.read().decode()
except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP {e.code} from {path}")
except Exception as e:
raise RuntimeError(f"Request failed: {e}")
print("=== qBittorrent Python Health Check ===")
print(f"Host: {QB_HOST}\n")
# 1. App version — no auth required
try:
version = api_get("/api/v2/app/version")
check("App version API", True, f"qBittorrent v{version.strip()}")
except RuntimeError as e:
check("App version API", False, str(e))
# 2. Authenticate — POST username/password, get SID cookie
authenticated = False
try:
result = api_post("/api/v2/auth/login", {
"username": QB_USERNAME,
"password": QB_PASSWORD,
})
if result.strip() == "Ok.":
authenticated = True
check("Authentication", True, f"logged in as '{QB_USERNAME}'")
else:
check("Authentication", False, f"unexpected response: {result.strip()}")
except RuntimeError as e:
check("Authentication", False, str(e))
# 3. Transfer info — download/upload speed and DHT status
if authenticated:
try:
transfer = api_get_json("/api/v2/transfer/info")
dl_speed = transfer.get("dl_info_speed", 0)
up_speed = transfer.get("up_info_speed", 0)
dht_nodes = transfer.get("dht_nodes", 0)
dht_ok = dht_nodes > 0
check(
"Transfer info",
True,
f"DL {dl_speed // 1024} KB/s, UP {up_speed // 1024} KB/s",
)
check(
"DHT nodes",
dht_ok,
f"{dht_nodes} nodes connected"
+ ("" if dht_ok else " — DHT disabled or no peers; public torrents may be affected"),
)
except RuntimeError as e:
check("Transfer info", False, str(e))
else:
check("Transfer info", False, "skipped — not authenticated")
# 4. Torrent list — breakdown by state
if authenticated:
try:
torrents = api_get_json("/api/v2/torrents/info")
states = {}
for t in torrents:
state = t.get("state", "unknown")
states[state] = states.get(state, 0) + 1
error_count = states.get("error", 0) + states.get("missingFiles", 0)
total = len(torrents)
summary = ", ".join(f"{v} {k}" for k, v in sorted(states.items()))
ok = error_count == 0
check(
"Torrent states",
ok,
f"{total} total: {summary}"
+ ("" if ok else f" — {error_count} in error/missingFiles state"),
)
except RuntimeError as e:
check("Torrent states", False, str(e))
else:
check("Torrent states", False, "skipped — not authenticated")
# 5. Global sync stats — cumulative download/upload
if authenticated:
try:
maindata = api_get_json("/api/v2/sync/maindata")
server_state = maindata.get("server_state", {})
dl_total = server_state.get("dl_info_data", 0) // (1024 ** 3)
up_total = server_state.get("up_info_data", 0) // (1024 ** 3)
free_space = server_state.get("free_space_on_disk", 0) // (1024 ** 3)
ok = free_space > 5
check(
"Disk free space",
ok,
f"{free_space} GB free on download disk"
+ ("" if ok else " — critically low, downloads will stall"),
)
check("Session totals", True, f"downloaded {dl_total} GB, uploaded {up_total} GB")
except RuntimeError as e:
check("Global sync stats", False, str(e))
else:
check("Global sync stats", False, "skipped — not authenticated")
# Summary
print(f"\n=== Result: {len(results) - failures} passed, {failures} failed ===")
sys.exit(0 if failures == 0 else 1)
Common qBittorrent Outage Causes
| Symptom | Likely Cause | Resolution |
|---|---|---|
| *arr apps report download client connection failed | Web UI authentication failure — password changed or session expired | Update credentials in Sonarr/Radarr/Lidarr Settings → Download Clients; verify password in qBittorrent Web UI settings |
| All torrents stall at a fixed percentage | Download directory disk full — writes blocked | Run df -h; free space on the download drive; resume paused torrents after cleanup |
| Torrent shows 0% with no peers connecting | Tracker unreachable — domain blocked, DNS failure, or tracker offline | Check tracker URL in torrent details; test tracker reachability; try an alternative tracker via magnet link |
| Public torrents find no peers | DHT disabled — required for trackerless peer discovery | Tools → Options → BitTorrent → enable DHT, PeX, and Local Peer Discovery |
| Traffic routing through wrong network interface | Bind IP misconfigured — all traffic goes through unintended interface | Tools → Options → Advanced → Network Interface; set correct interface or IP binding for VPN/LAN |
| *arr apps queue unlimited downloads until disk is full | Free disk space check not configured in qBittorrent or *arr apps | Set a disk space reserve in qBittorrent (Options → Downloads); configure max queue size in each *arr app |
Architecture Overview
| Component | Function | Failure Impact |
|---|---|---|
| qBittorrent-nox (C++) | Main headless process — torrent engine, Web API, RSS | Total failure; all downloads stop and Web API becomes unreachable |
| Web UI / Web API | HTTP API on port 8080 — used by *arr apps and browser | *arr apps lose download client; no new torrents can be queued |
| BitTorrent Engine (libtorrent) | Peer discovery, piece management, tracker communication | Downloads stall; existing connections drop; tracker announces fail |
| DHT / PeX | Decentralized peer discovery for public torrents | Public torrents find no peers if disabled; private trackers unaffected |
| Download Storage | Target disk for downloaded files before import by *arr apps | All active downloads stall when disk reaches capacity |
| RSS Downloader | Polls RSS feeds to auto-add torrents matching rules | RSS-triggered downloads stop; manually added torrents unaffected |
Uptime History
| Date | Incident Type | Duration | Impact |
|---|---|---|---|
| 2026-01 | Web UI password changed — *arr apps lost connection | ~10 hrs undetected | Sonarr and Radarr queued grabs internally but no torrents were sent to qBittorrent |
| 2025-11 | Download disk full — all active transfers stalled | ~3 hrs | All in-progress downloads froze at current progress; no new torrents completing |
| 2025-08 | Bind IP set to VPN interface — VPN disconnected | ~6 hrs | All torrent traffic dropped; qBittorrent appeared healthy but no data transferred |
| 2025-07 | DHT disabled after settings reset | ~12 hrs undetected | Public torrents found zero peers; private tracker torrents unaffected |
Monitor qBittorrent Automatically
qBittorrent failures are invisible to *arr apps until they try to queue a new download — by then your media pipeline may have been broken for hours. ezmon.com monitors your qBittorrent endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the Web API stops responding or authentication starts failing.