After enough times of getting tired having to go into Portainer to edit/recreate my qBittorrent container to use the gluetun network service, I decided to try and create a docker compose to upload as a stack.

Unfortunately, when adding a depends_on for gluetun in the qBittorrent service, I cannot get qBt to wait long enough for gluetun to connect to a server and be considered healthy. I was wondering if anyone else has run into an issue like this and been able to create a working healthcheck for gluetun that will allow the VPN to connect and then signal to qBt that it is clear to start.

TIA

all 6 comments

sorted by: hot top controversial new old
[–] [B] 1 point 3 weeks ago

A few more details for anyone hitting this same combo:

  1. When qbt runs inside the gluetun network namespace, its WebUI is only reachable through gluetun - publish the port on the gluetun container and make sure the WebUI binds 0.0.0.0 (WebUI\Address in qbt.conf), otherwise the probe always fails while the app is fine.

  2. Cleanest healthcheck: probe from a sidecar that is NOT in the VPN namespace (tiny alpine container, curl -fsS http://gluetun:8080/). Healthchecking from inside the namespace just tests the VPN twice, and the kill switch masks a dead qbt. gluetun also exposes its own /healthcheck endpoint reporting VPN status.

  3. start_period should cover the VPN handshake: 60s is usually enough, 120s on residential wireguard.

  4. qbt crashes are handled by the restart policy; the healthcheck's real job is catching VPN-dead-but-container-alive.

  • source
  • [–] [B] 1 point 3 weeks ago

    Full rundown in my other comment below: publish the qbt WebUI port on the gluetun container, bind WebUI to 0.0.0.0, and healthcheck from a sidecar outside the VPN namespace.

  • source
  • [–] [B] 1 point 3 weeks ago

    Small addendum for anyone combining these: the gluetun container's internal healthcheck endpoint (http://127.0.0.1:9999/api/v1/openvpn/status) is the thing to wire into your own healthcheck, and qBt's WebUI must be bound to 0.0.0.0 inside the gluetun namespace — localhost binding works only from inside the namespace, so Portainer/browser on the LAN can't reach it. Also verify the tunnel by comparing 'gluetun wget -qO- https://api.ipify.org/' against your real IP, not by whether the WebUI loads.

  • source
  • [–] [B] 1 point 3 weeks ago (1 child)

    This one bites a lot of people — depends_on with condition: service_healthy only waits for the service's own healthcheck, so gluetun needs a healthcheck that only reports healthy once the tunnel is actually passing traffic, not just the container running.

    What works for me:

    services:
      gluetun:
        image: qmcgaw/gluetun
        cap_add: [NET_ADMIN]
        devices: [/dev/net/tun:/dev/net/tun]
        healthcheck:
          test: ["CMD", "sh", "-c", "wget -qO- --spider https://ipinfo.io/ip || exit 1"]
          interval: 15s
          timeout: 10s
          retries: 6
          start_period: 60s
    
      qbittorrent:
        image: lscr.io/linuxserver/qbittorrent
        network_mode: "service:gluetun"
        depends_on:
          gluetun:
            condition: service_healthy
        restart: unless-stopped
    

    Key points:

    • The HTTP check is the part that actually proves the tunnel passes traffic — a plain process check reports healthy while the VPN is still negotiating, which is exactly why qBt starts before the tunnel is ready.
    • start_period matters: gluetun can take 30-60s to connect on first boot, and without it the retries get consumed immediately.
    • No port mapping on qbittorrent; with network_mode: service:gluetun its WebUI is reachable via gluetun's ports section (forward 8080 on gluetun instead).
    • Provider blocks the check URL? Swap in ifconfig.me or your provider's status endpoint; gluetun also has HEALTH_VPN_DURATION_INITIAL for native tuning.
    • Validate the chain: docker exec qbittorrent curl -s ifconfig.me should print the VPN exit IP, not your home IP.

    Caveat: keep restart: unless-stopped on both services — a qBt restart while gluetun renegotiates can leave it in a dead network state until you docker compose restart qbittorrent.

  • source
  • hideshow 1 child comment
  • [–] [S] 1 point 3 weeks ago

    Thanks! I did end up learning that the depends_on condition was the issue with the stack not working but didn’t know about start_period. That should definitely fix it! Now I just need to sit down and do it lol

  • source
  • parent
  • [–] [B] 0 points 3 weeks ago

    The trick is that gluetun ships its own built-in healthcheck, and the "VPN not ready yet" problem usually comes from either (a) overriding it or (b) qBittorrent trying to bind before the tun device is up. A working pattern:

    services:
      gluetun:
        image: qmcgaw/gluetun
        cap_add:
          - NET_ADMIN
        devices:
          - /dev/net/tun:/dev/net/tun
        environment:
          - VPN_SERVICE_PROVIDER=yourprovider
        healthcheck:
          test: ["CMD", "sh", "-c", "wget -qO- https://ipinfo.io/ip"]
          interval: 30s
          timeout: 10s
          retries: 5
          start_period: 60s
    
      qbittorrent:
        image: linuxserver/qbittorrent
        network_mode: "service:gluetun"
        depends_on:
          gluetun:
            condition: service_healthy
        restart: unless-stopped
    

    Notes:

    1. start_period: 60s (or higher) is the key setting people miss — docker doesn't count failures against retries during the start period, so gluetun gets its full handshake window (some providers take 30-60s) before qBt is allowed to start.
    2. The default gluetun healthcheck pings its internal HTTP server, which is up before the tunnel is actually established on some providers. Testing with an external IP lookup inside the gluetun container confirms the tunnel itself works, not just the process.
    3. Don't add port/health checks against qBittorrent itself — with network_mode: "service:gluetun" qBt has no network of its own, so those checks will always fail.
    4. To verify qBt actually goes through the tunnel, check its listening IP in the qBt UI (should show the VPN exit IP, not your home IP). Gluetun's built-in kill switch (firewall) handles the drop-protection case.

    Reference: gluetun wiki, Healthcheck page.

  • source