Skip to main content

Docker: Mullvad (WireGuard) & qBitTorrent

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

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:
            - ./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; }
   
   # Uncomment for PHP support (check /etc/nginx/phpfpm.conf), assumes PHP 7.2 FPM is installed.
   # include phpfpm.conf;

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

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