Skip to content

Repository files navigation

gluetun-qbt-watchdog

A lightweight Docker container that keeps Gluetun VPN port forwarding and qBittorrent in sync automatically.

The Problem

When Gluetun port forwarding is enabled, NAT-PMP renewal can fail while the VPN tunnel stays up. Docker still sees the container as healthy, but qBittorrent loses its reachable listen port and torrent connectivity degrades.

Common workarounds such as VPN_PORT_FORWARDING_UP_COMMAND, Gluetun healthchecks, autoheal containers, or cron scripts each cover only part of the failure mode and can introduce race conditions or container restart problems.

Related Gluetun issues:

The Solution

Every cycle, the watchdog:

  1. Reads Gluetun's forwarded port from the HTTP control API.
  2. Recovers missing ports by restarting the VPN through Gluetun's API, then restarting containers only as a last resort.
  3. Authenticates to qBittorrent using either API-key auth or legacy username/password auth.
  4. Updates qBittorrent's listen_port when it differs from Gluetun's forwarded port.
  5. Logs periodic heartbeats so you know it is alive.

Example logs:

2026-03-19 09:29:04 WARNING: Port mismatch: gluetun=47830, qbt=34987. Fixing...
2026-03-19 09:29:07 Port synced successfully: 47830
2026-03-19 09:34:14 OK: gluetun=47830, qbt=47830
2026-03-19 15:01:45 WARNING: No forwarded port from gluetun. Attempting VPN restart...
2026-03-19 15:02:03 New port after recovery: 52194

Quick Start

1. Generate a Gluetun API key

docker run --rm qmcgaw/gluetun genkey

Configure Gluetun's control server with that key:

environment:
  - HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE={"auth":"apikey","apikey":"${GLUETUN_API_KEY}"}

2. Configure qBittorrent authentication

For qBittorrent 5.2.0 / WebAPI 2.14.1+, API-key auth is preferred:

  1. Open qBittorrent WebUI.
  2. Go to Preferences → WebUI → API Key.
  3. Generate an API key.
  4. Set QBT_API_KEY in .env.

If you have not generated an API key yet, or you are using an older qBittorrent version, leave QBT_API_KEY empty and use QBT_USER / QBT_PASS instead.

3. Create .env

cp .env.example .env

Example:

GLUETUN_API_KEY=YourGeneratedGluetunKeyHere

# Preferred for qBittorrent 5.2.0+
QBT_API_KEY=qbt_yourGeneratedQbitApiKeyHere
QBT_AUTH_MODE=auto

# Legacy fallback / first-start recovery
QBT_USER=admin
QBT_PASS=yourpassword

4. Add the watchdog service

Use the published GHCR image:

gluetun-qbt-watchdog:
  image: ghcr.io/brunoorsolon/gluetun-qbt-watchdog:latest
  container_name: gluetun-qbt-watchdog
  restart: unless-stopped
  networks:
    - vpn-net
  depends_on:
    - gluetun
    - qbittorrent
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock
  environment:
    - GLUETUN_CONTAINER_NAME=${GLUETUN_CONTAINER_NAME:-gluetun}
    - GLUETUN_API=http://${GLUETUN_CONTAINER_NAME:-gluetun}:${GLUETUN_API_PORT:-8000}
    - GLUETUN_API_KEY=${GLUETUN_API_KEY}
    - QBT_CONTAINER_NAME=${QBT_CONTAINER_NAME:-qbittorrent}
    - QBT_API=http://${GLUETUN_CONTAINER_NAME:-gluetun}:${QBT_WEBUI_PORT:-8080}
    - QBT_API_KEY=${QBT_API_KEY:-}
    - QBT_AUTH_MODE=${QBT_AUTH_MODE:-auto}
    - QBT_USER=${QBT_USER:-admin}
    - QBT_PASS=${QBT_PASS:-}
    - CHECK_INTERVAL=${CHECK_INTERVAL:-60}
    - HEARTBEAT_CYCLE_FREQUENCY=${HEARTBEAT_CYCLE_FREQUENCY:-10}
    - MAX_RESTART_WAIT=${MAX_RESTART_WAIT:-120}
    - ADDITIONAL_RESTART=${ADDITIONAL_RESTART:-}

A complete example is available in docker-compose.yml.example.

5. Pull the image

docker pull ghcr.io/brunoorsolon/gluetun-qbt-watchdog:latest

You can also pin a release tag once releases are published:

image: ghcr.io/brunoorsolon/gluetun-qbt-watchdog:1.0.0

Configuration

Variable Default Description
GLUETUN_CONTAINER_NAME gluetun Docker container name for Gluetun.
GLUETUN_API http://gluetun:8000 Gluetun HTTP control API URL.
GLUETUN_API_KEY empty API key generated by docker run --rm qmcgaw/gluetun genkey.
QBT_CONTAINER_NAME qbittorrent Docker container name for qBittorrent.
QBT_API http://gluetun:8080 qBittorrent WebAPI URL. Use the Gluetun service when qBittorrent shares Gluetun's network namespace.
QBT_API_KEY empty qBittorrent 5.2.0+ API key. Preferred when available.
QBT_AUTH_MODE auto auto, apikey, or password.
QBT_USER admin qBittorrent username for legacy password auth.
QBT_PASS empty qBittorrent password for legacy password auth and first-start recovery.
CHECK_INTERVAL 60 Seconds between sync cycles.
HEARTBEAT_CYCLE_FREQUENCY 10 Log an OK heartbeat every N successful cycles.
MAX_RESTART_WAIT 120 Seconds to wait for a forwarded port after full restart.
ADDITIONAL_RESTART empty Optional space-separated container names to restart after Gluetun/qBittorrent.

qBittorrent Authentication Modes

QBT_AUTH_MODE=auto is the recommended default:

  • If QBT_API_KEY is set, the watchdog uses qBittorrent API-key auth with Authorization: Bearer <QBT_API_KEY>.
  • If QBT_API_KEY is empty, the watchdog falls back to QBT_USER / QBT_PASS cookie login.

Other modes:

  • QBT_AUTH_MODE=apikey requires QBT_API_KEY and never calls qBittorrent auth endpoints.
  • QBT_AUTH_MODE=password always uses QBT_USER / QBT_PASS.

A configured but rejected API key is treated as a configuration error and does not silently fall back to the password. Leave QBT_API_KEY empty if you intentionally want password auth.

Password auth supports both legacy qBittorrent login success (200 with Ok.) and qBittorrent 5.2.0 login success (204 No Content).

qBittorrent Password Recovery

In password mode, if the watchdog cannot log in with QBT_PASS, it tries to recover automatically:

  1. Read the temporary password from qBittorrent container logs.
  2. Log in with that temporary password.
  3. Update qBittorrent's WebUI password to QBT_PASS.
  4. Retry normal login and continue the sync loop.

This is useful on first startup with linuxserver/qbittorrent, where qBittorrent may generate a temporary password before you set your own.

Password recovery is not used in API-key mode. If you are setting up a new qBittorrent 5.2.0 instance, you can start with password mode, generate an API key in the WebUI, then switch back to QBT_AUTH_MODE=auto with QBT_API_KEY set.

Recovery Behavior

When Gluetun has no forwarded port:

  1. Restart the VPN through Gluetun's HTTP API.
  2. Wait briefly and check the forwarded port again.
  3. If still missing, restart Gluetun and qBittorrent containers through Docker.
  4. Restart any containers listed in ADDITIONAL_RESTART.
  5. Give up after MAX_RESTART_WAIT and retry next cycle.

Network Architecture

┌─────────────────────────────────────────────────────┐
│ vpn-net (Docker bridge network)                     │
│                                                     │
│  ┌───────────┐   ┌──────────────┐   ┌────────────┐  │
│  │ gluetun   │   │ qbittorrent  │   │ watchdog   │  │
│  │ :8000 API │   │ :8080 WebUI  │   │            │  │
│  │ :8080 ←── ┼───┤ (shares net) │   │ curl → API │  │
│  └───────────┘   └──────────────┘   └────────────┘  │
│       │              network_mode:                  │
│       │              service:gluetun                │
└───────┼─────────────────────────────────────────────┘
        │
   VPN tunnel → ProtonVPN → NAT-PMP port forwarding

qBittorrent usually uses network_mode: "service:gluetun", so it shares Gluetun's network namespace. The watchdog is on vpn-net independently and talks to both services through Gluetun's exposed ports/DNS name.

Docker Socket Access

The watchdog mounts /var/run/docker.sock so it can:

  • restart containers as a last-resort recovery path
  • read qBittorrent logs for legacy password recovery

Docker socket access allows container control from inside the watchdog container. Only run this in an environment where you trust the image and Compose configuration.

What You Can Remove

If the watchdog is working for your stack, you generally do not need:

  • VPN_PORT_FORWARDING_UP_COMMAND / VPN_PORT_FORWARDING_DOWN_COMMAND on Gluetun
  • Docker healthchecks on Gluetun / qBittorrent solely for port-forward recovery
  • willfarrell/autoheal or similar auto-restart containers for this issue
  • cron jobs that check port files or restart containers

Local Build / Development

The recommended deployment uses GHCR. To build locally instead:

gluetun-qbt-watchdog:
  build: ./gluetun-qbt-watchdog

The build context contains:

gluetun-qbt-watchdog/
├── Dockerfile
└── gluetun-qbt-watchdog.sh

Publishing / Releases

The GitHub Actions workflow publishes to:

ghcr.io/brunoorsolon/gluetun-qbt-watchdog

Publishing behavior:

  • Push to main: publishes latest and the branch tag.
  • Push v*.*.*: publishes semver tags such as 1.2.3 and 1.2.
  • Manual dispatch: publishes tags derived from the selected ref.

After the first GHCR publish, make the package public in GitHub package settings if unauthenticated pulls should work.

Requirements

  • Gluetun with VPN_PORT_FORWARDING=on and HTTP control server enabled.
  • qBittorrent with WebUI/WebAPI enabled.
  • Docker socket access for restart fallback and legacy password recovery.
  • Shared Docker network where the watchdog can reach Gluetun and qBittorrent WebAPI.

Tested With

  • Gluetun latest (v3.40+)
  • ProtonVPN WireGuard port forwarding
  • qBittorrent 5.2.0 / WebAPI 2.14.1+ API-key auth
  • qBittorrent legacy username/password auth
  • qBittorrent linuxserver image
  • TrueNAS SCALE + Docker
  • Alpine 3.20 base image

Adapting for Other Torrent Clients

The script is written for qBittorrent, but the pattern works for any torrent client with a web API. You would need to modify the qBittorrent auth, port read, and port write helpers.

License

MIT — do whatever you want with it.

About

A lightweight Docker container that keeps Gluetun VPN port forwarding and qBittorrent in sync — automatically.

Resources

Stars

22 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages