Skip to main content

Docker: Mullvad (WireGuard) & qBitTorrent

Sample Docker Compose configuration for running qBitTorrent as a container routed through another Mullvad container.

Port forwarding

For port forwarding to work via Mullvad, you have to set the "Listening port" (Settings -> Connection -> Listening port) in qBitTorrent to the correct forwarded port from Mullvad.

Screenshot of listening port settings page

I also recommend forcing qBitTorrent to use the Mullvad network interface (Advanced -> Network interface), though I'm not sure if it matters much

Screenshot of network interface settings page

docker-compose.yml file

version: '3.5'
services:
    vpn:
        container_name: wireguard
        image: jordanpotter/wireguard
        cap_add:
            - NET_ADMIN
            - SYS_MODULE
        sysctls:
            net.ipv4.conf.all.src_valid_mark: 1
            net.ipv6.conf.all.disable_ipv6: 0
        volumes:
            # Generate a file here: https://mullvad.net/en/account/#/wireguard-config/
            # and put it in the same folder as `docker-compose.yml`
            # Make sure to name it `mullvad.conf` exactly.
            # Or change the line below to match with your local `mullvad.conf` file.
            - ./mullvad.conf:/etc/wireguard/mullvad.conf
        ports:
            # I run NGINX on the host server for a reverse proxy.
            # The secondary `8888` matches the `WEBUI_PORT` on the qBitTorrent container.
            - "127.0.0.1:8080:8888"
        restart: unless-stopped

    qbittorrent:
        container_name: qbit-wg
        image: ghcr.io/linuxserver/qbittorrent
        depends_on:
            - vpn
        network_mode: "service:vpn"
        restart: unless-stopped
        volumes:
            # For persisting qBitTorrent configuration files
            - ./config:/config
            # Hard drives with more storage space, for download directories.
            # - /data/media:/data/media
        environment:
            - PUID=1000
            - PGID=1000
            - TZ=Europe/Oslo
            - WEBUI_PORT=8888

NGINX configuration file

# This is loosely based on the following:
# qBitTorrent GitHub wiki: https://github.com/qbittorrent/qBittorrent/wiki/NGINX-Reverse-Proxy-for-Web-UI
# My personal NGINX bootstrapping script: https://gist.github.com/Decicus/2f09db5d30f4f24e39de3792bba75b72
# Someone's reddit comment while I was researching, I wish I could find it again...

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name qbittorrent.example.com;
    root /var/www/html;

    ssl_certificate /srv/ssl/qbittorrent/fullchain.pem;
    ssl_certificate_key /srv/ssl/qbittorrent/key.pem;

    server_tokens off;
    
    # These files are included as part of my NGINX bootstrapping script: https://gist.github.com/Decicus/2f09db5d30f4f24e39de3792bba75b72
    # So normally I'd just put `include ssl_params.conf` and call it a day.
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # dhparams will have to be created here
    # See this script file: https://gist.github.com/Decicus/2f09db5d30f4f24e39de3792bba75b72#file-generate-dhparams-sh
    ssl_dhparam /etc/nginx/dhparams.pem;
    ssl_session_cache builtin:1000 shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";

    # CSP that whitelists a userstyle I use in my browser, for the qBitTorrent web UI: https://docs.theme-park.dev/themes/qbittorrent/
    # Might wanna tweak it to your own liking if you don't use it.
    # Commented by default, because it generally causes more headaches than it helps...
    # add_header Content-Security-Policy "default-src https://use.fontawesome.com https: moz-extension: 'self' 'unsafe-inline'; style-src https://gilbn.github.io https://theme-park.dev https://qbittorrent.example.com https://raw.githubusercontent.com https://use.fontawesome.com 'unsafe-inline'; img-src https:";

    index index.nginx-debian.html index.html index.htm;

    charset utf-8;

    location / {
        # IP-based whitelisting. I whitelist my home IP, but you can choose to use something like HTTP auth if you want.
        # Some might also be fine with exposing qBitTorrent's login page directly.
        # allow 127.0.0.1;
        # allow 192.168.0.0/24;
        # deny all;

        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        http2_push_preload on; # Enable http2 push

        # The `8888` port here has to match the one in the `WEBUI_PORT` in docker-compose.yml
        # Or else you will have issues...
        proxy_set_header   Host               127.0.0.1:8888;
        proxy_set_header   X-Forwarded-Proto  $scheme;
        proxy_set_header   X-Forwarded-Host   $http_host;
        proxy_set_header   X-Forwarded-For    $remote_addr;
        proxy_set_header   X-Real-IP          $remote_addr;
        proxy_hide_header   Content-Security-Policy;

        # optionally, you can adjust the POST request size limit, to allow adding a lot of torrents at once:
        #client_max_body_size 100M;

        # since v4.2.2, is possible to configure qBittorrent
        # to set the "Secure" flag for the session cookie automatically.
        # However, that option does nothing unless using qBittorrent's built-in HTTPS functionality.
        # For this use case, where qBittorrent itself is using plain HTTP
        # (and regardless of whether or not the external website uses HTTPS),
        # the flag must be set here, in the proxy configuration itself:
        proxy_cookie_path / "/; Secure";
    }

    location /.well-known {
        auth_basic "off";
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log /var/log/nginx/qbittorrent-access.log combined;
    error_log  /var/log/nginx/qbittorrent-error.log error;

    location ~ /\.ht {
        deny all;
    }
}