Qbit WebUi Unreachable

Summary

The qBittorrent Web UI is unreachable because its network namespace is entirely isolated within the Gluetun VPN container. While Gluetun exposes the torrent port to the host via port mapping (6881), its internal port 8080 (the Web UI) is not exposed to the host in a standard way. Furthermore, attempting to reach qBittorrent via the host IP on port 8080 fails because traffic cannot traverse the docker network bridge into the service:gluetun namespace automatically.

Root Cause

  1. Missing Port Mapping on Gluetun:
    In the gluetun service, only the VPN input port (${FIREWALL_VPN_INPUT_PORTS}) is published.

    ports:
      - ${FIREWALL_VPN_INPUT_PORTS}:${FIREWALL_VPN_INPUT_PORTS}

    Port 8080 (the qBittorrent Web UI) is not listed here. Without this mapping, the host machine cannot reach port 8080 inside the gluetun container.

  2. Incorrect WebUI Configuration in qBittorrent:
    The qBittorrent service configures its internal environment to listen on port 8080, but the container is sharing its network stack with Gluetun (network_mode: service:gluetun). This means qBittorrent listens on the Gluetun container’s IP (172.39.0.2), not the Host IP. Since Gluetun doesn’t forward port 8080 to the host, the UI is inaccessible from outside the Gluetun container.

  3. Host-to-Container Routing Failure:
    Even though the servarrnetwork exists, the qBittorrent service does not explicitly attach to it because it uses network_mode: service:gluetun. Therefore, it inherits the Gluetun network interface, which is not bridged to the servarrnetwork on the host.

Why This Happens in Real Systems

  • Split-Tunneling Configuration: Developers often assume that port mapping on the VPN container (Gluetun) automatically forwards all ports or that the dependent container (qBittorrent) will inherit the ability to communicate with the host network seamlessly.
  • Incorrect Stack Architecture: When sharing a network namespace via network_mode: service:X, the dependent container loses its ability to publish ports independently. It relies entirely on the parent container (Gluetun) to publish those ports to the host.
  • Variable Overrides: The environment variable WEBUI_PORT=8080 dictates the internal listening port, but the Docker Compose file fails to translate this internal port to an external host port.

Real-World Impact

  • Operational Inaccessibility: The admin cannot access the qBittorrent Web UI to manage torrents, configure settings, or verify connectivity.
  • Service Degradation: The *arr stack integration is hampered because manual torrent management is blocked, potentially causing automation failures.
  • Debugging Complexity: Logs indicate successful startup, masking the fact that the network topology is misconfigured. This leads to prolonged troubleshooting cycles.

Example or Code

To resolve this, we must expose 8080 on the Gluetun container so the host can reach it, and ensure qBittorrent is configured to use that port.

1. Corrected Gluetun Service (Add port 8080):

services:
  gluetun:
    image: qmcgaw/gluetun
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    ports:
      # Add 8080 here
      - 8080:8080
      # Existing ports
      - ${FIREWALL_VPN_INPUT_PORTS}:${FIREWALL_VPN_INPUT_PORTS}
      - 6881:6881
      # ... other ports

2. Corrected qBittorrent Service (Network Mode Adjustment):
Note: To make the Web UI reachable, qBittorrent must be accessible via the Gluetun IP, but the port must be mapped on Gluetun. Alternatively, and more robustly, attach qBittorrent to the servarrnetwork so it is independently reachable by other services (like Sonarr/Radarr) and map the port directly on qBittorrent. However, strictly fixing the “Unreachable” issue while maintaining VPN traffic for qBittorrent requires the Gluetun port mapping.

If you strictly want to keep qBittorrent behind Gluetun (for VPN traffic only) and access the UI via the Host:

  1. Add port mapping to Gluetun (as shown above).
  2. Ensure qBittorrent’s WEBUI_PORT matches the internal port Gluetun listens on (8080).

3. Alternative: Hybrid Network (Recommended for Accessibility):
If you want Sonarr/Radarr (on 172.39.0.x) to talk to qBittorrent, network_mode: service:gluetun prevents this. You should allow qBittorrent to attach to the servarrnetwork as well.

qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    # Remove network_mode: service:gluetun if you need external access
    # network_mode: "service:gluetun" 
    # Add network_mode to use Gluetun for VPN but keep bridge for UI
    network_mode: "container:gluetun" 
    # OR configure routes manually. 
    # To fix strictly without changing routing logic:
    # Keep 'network_mode: service:gluetun'
    # Ensure Gluetun ports are mapped correctly (Step 1)

How Senior Engineers Fix It

Senior engineers resolve this by enforcing Explicit Port Forwarding and Network Topology Clarity:

  1. Map External Ports to the VPN Container: Since qBittorrent shares the network namespace with Gluetun, Gluetun acts as the gateway. We must map the Web UI port (8080) on the host to port 8080 inside the Gluetun container.
  2. Verify Internal Configuration: We ensure WEBUI_PORT=8080 is set correctly in the qBittorrent environment variables so the application listens on the port Gluetun is forwarding.
  3. Split Network vs. Service Mode: For complex stacks, we often abandon network_mode: service:X for stateful applications like qBittorrent. Instead, we connect qBittorrent to the servarrnetwork (like Sonarr/Radarr) and use iptables inside the container or a shared VPN network (like macvlan) to route traffic through Gluetun.
  4. Validation: We use curl from the host to verify reachability: curl -v http://localhost:8080. If this fails, the port mapping is still incorrect.

Why Juniors Miss It

  1. Misunderstanding network_mode: Juniors often believe that network_mode: service:gluetun simply routes traffic through the VPN for internet access while keeping the container on the host’s bridge network. In reality, it completely isolates the container’s network stack, making it invisible to the host on all ports except those explicitly mapped by the parent container (Gluetun).
  2. Confusing Internal vs. External Ports: They set WEBUI_PORT=8080 (internal) but forget that Docker requires a ports mapping (external:internal) to access it from the browser.
  3. Log Blindness: Since the logs say “Listening on port 8080,” juniors assume the port is open to the network. They fail to check if the port is actually bound to the host interface (netstat -tuln or docker ps).
  4. Overlooking the VPN Container as a Firewall: They treat Gluetun as a transparent bridge. However, Gluetun (based on Alpine Linux) often has strict firewall rules (nftables/iptables) that drop incoming connections to non-forwarded ports. The VPN container acts as a firewall/router; if a port isn’t in the config, it’s blocked.