The Shielded Tunnel: Securing Your Traffic with Gluetun & Docker
In my previous guides, we built the server and we built the automation stack. Now, we need to talk about Operational Security (OpSec).
I've been using various types of VPN tech personally and professionally for two decades by this point and they have assisted me greatly, in both respects. I consider VPNs to be crucial as a tool for online security & privacy, and simply to access what I need at home. Especially when I am travelling or otherwise living a bit as a "digital nomad".
Whether you are sailing the high seas of the internet or just want to keep your ISP's nose out of your business, a VPN is non-negotiable. But running a VPN on your entire server is messy. It breaks remote access, screws with DNS, confuses Plex & Jellyfin, and adds latency where you don't want it. We're going to carve up your homelab with a scalpel, not bash it with a sledge hammer.
The solution? Split-Tunneling via Docker.
We are going to use Gluetun, a lightweight, "Swiss Army Knife" VPN client container. It acts as a secure gateway. We will configure specific containers (like your download clients) to route through Gluetun, while the rest of your server stays fast and directly connected.
This guide covers:
- Setting up the Gluetun container.
- Configuration examples for NordVPN, PIA, and Mullvad.
- How to "attach" other containers (NZBGet, qBittorrent, etc.) to the tunnel.
π οΈ Step 1: The Master Container (Gluetun)
Gluetun is unique. It doesn't just connect to a VPN; it acts as a network supervisor. If the VPN connection drops, Gluetun acts as a "Kill Switch," instantly cutting networking to any container attached to it. No leaks. Ever.
Additionally there's a LOT of settings you can configure regarding DNS for Gluetun, but that's more advanced and might be worth a part 2. General rule of thumb if you have DNS issues - check to see which DNS resolver you have set in your system with cat /etc/resolv.conf. Full explanation of DNS settings for Gluetun here. Notice the defaults first. Don't change anything here unless you need to.
Here's a logical diagram of Gluetun in action -
flowchart TB
%% ==========================
%% 1. Local Network
subgraph Local ["π Home / Local Network"]
Laptop["π» User / Laptop"]
Router["π§± EDGE Router / Firewall"]
end
%% ==========================
%% 2. Docker Host & Gluetun Stack
subgraph Host ["π³ Docker Host Server"]
%% Gluetun Namespace
subgraph VPN_Stack ["π‘οΈ Gluetun Network Namespace"]
Gluetun["π‘οΈ Gluetun Container"]
%% Apps routed through Gluetun
subgraph Apps ["Protected Apps"]
qBit["β¬οΈ qBittorrent"]
Prowl["π Prowlarr"]
NZB["π¦ NZBGet"]
end
end
end
%% ==========================
%% 3. The Tunnel
subgraph Tunnel ["π The Encrypted Tunnel"]
Nord["π‘οΈ VPN Provider (Nord / PIA / Mullvad)"]
end
%% ==========================
%% 4. The Internet
subgraph Internet ["π The Public Internet"]
ISP["π’ ISP (Rogers / Bell)"]
Swarm["π Torrent Swarm"]
Indexers["ποΈ Usenet / Trackers"]
end
%% ==========================
%% CONNECTIONS (Top-Down Flow)
Laptop -->|"WebUI Ports 8080 / 9696"| Gluetun
Gluetun -.->|"Internal Port Mapping"| Apps
Apps ==>|"All Traffic"| Gluetun
Gluetun ==>|"WireGuard / OpenVPN"| Nord
Nord ==>|"Masked IP"| Swarm
Nord ==>|"Masked IP"| Indexers
%% Kill Switch Logic
Gluetun -.->|"β BLOCKED (Kill Switch)"| ISP
Apps -.->|"β NO DIRECT ACCESS"| Router
%% ==========================
%% STYLING
style Laptop fill:#f77f00,stroke:#d95d39,stroke-width:2px,color:#111
style Router fill:#457b9d,stroke:#1d3557,stroke-width:2px,color:#fff
style Gluetun fill:#0033a0,stroke:#001f66,stroke-width:4px,color:#fff
style qBit fill:#d62828,stroke:#a71d2a,stroke-width:2px,color:#fff
style Prowl fill:#d62828,stroke:#a71d2a,stroke-width:2px,color:#fff
style NZB fill:#d62828,stroke:#a71d2a,stroke-width:2px,color:#fff
style Nord fill:#0033a0,stroke:#001f66,stroke-width:4px,color:#fff
style ISP fill:#555,stroke:#333,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
style Swarm fill:#ffcc00,stroke:#996600,stroke-width:2px,color:#111
style Indexers fill:#ffcc00,stroke:#996600,stroke-width:2px,color:#111
style Local fill:transparent,stroke:#888,stroke-width:1px,stroke-dasharray:4 4
style Host fill:#f1f1f1,stroke:#333,stroke-width:2px,color:#333
style VPN_Stack fill:#e0e7ff,stroke:#0033a0,stroke-width:2px,stroke-dasharray:5 5,color:#0033a0
style Tunnel fill:transparent,stroke:#0055aa,stroke-width:3px,stroke-dasharray:2 2
style Internet fill:transparent,stroke:#aa0000,stroke-width:1px,stroke-dasharray:4 4
The Base Compose File
Here is the base docker-compose structure. You will choose ONE of the provider blocks below to fill in the environment variables. Their full list of compatible providers is here.
Create a new directory (e.g., /opt/appdata/gluetun or /opt/DOCKERS/gluetun) and a docker-compose.yml file.
version: "3"
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
# --- WEB UI PORTS ONLY ---
# We only map ports that YOU need to access from your LAN.
# We do NOT map torrent ports (like 6881) here because
# incoming VPN traffic is handled by the VPN provider, not Docker.
- "8080:8080" # qBittorrent Web UI
- "9696:9696" # Prowlarr Web UI
- "6789:6789" # NZBGet Web UI
volumes:
- /opt/appdata/gluetun:/gluetun
environment:
- TZ=America/Toronto
# --- VPN SETTINGS ---
# See full list of providers here:
# https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers
- VPN_SERVICE_PROVIDER=nordvpn
- VPN_TYPE=openvpn
- OPENVPN_USER=your_service_username
- OPENVPN_PASSWORD=your_service_password
- SERVER_REGIONS=Canada
#- FIREWALL_VPN_INPUT_PORTS=21754 #Only if specified by your VPN provider
#- FIREWALL_OUTBOUND_SUBNETS=10.0.0.0/16 #Only if you require a docker using Gluetun to have access to the outside world NOT using the VPN!
restart: alwaysSee: https://www.reddit.com/r/gluetun/comments/1p7htlb/comment/nqxyrl9/
Alternatively, you can simply add it to your existing docker-compose.yml if you'd prefer.
Compose Example with .env variables & Wireguard
services:
gluetun:
image: qmcgaw/gluetun:v3
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
- 8080:8080/tcp # qbittorrent
environment:
- TZ=${TZ}
- UPDATER_PERIOD=24h
- VPN_SERVICE_PROVIDER=protonvpn
- VPN_TYPE=${VPN_TYPE}
- BLOCK_MALICIOUS=off
- OPENVPN_USER=${OPENVPN_USER}
- OPENVPN_PASSWORD=${OPENVPN_PASSWORD}
- OPENVPN_CIPHERS=AES-256-GCM
- WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
- PORT_FORWARD_ONLY=on
- VPN_PORT_FORWARDING=on
- VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'wget -O- --retry-connrefused --post-data "json={\"listen_port\":{{PORTS}}}" http://127.0.0.1:8080/api/v2/app/setPreferences 2>&1'
- SERVER_COUNTRIES=${SERVER_COUNTRIES}
volumes:
- ${MEDIA_DIR}/gluetun/config:/gluetun
restart: unless-stopped
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
depends_on:
gluetun:
condition: service_healthy
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- WEBUI_PORT=8080
volumes:
- ${MEDIA_DIR}/qbittorrent/config:/config
- ${MEDIA_DIR}/qbittorrent/downloads:/downloads
restart: unless-stopped
network_mode: "service:gluetun"
.env here:
# Fill in either the OpenVPN or Wireguard sections. The choice of vpn is made with VPN_TYPE. Choose 'wireguard' or 'openvpn'. The settings for the other vpn type will be ignored.
# Alter the TZ, MEDIA_DIR, and SERVER_COUNTRIES to your preference. Run 'docker run --rm -v eraseme:/gluetun qmcgaw/gluetun format-servers -protonvpn' to get a list of server countries
# Base config
TZ=Australia/Brisbane
MEDIA_DIR=/media
# Gluetun config
VPN_TYPE=wireguard #openvpn
SERVER_COUNTRIES=Albania,Algeria,Angola,Argentina,Australia,Austria,Azerbaijan
# OpenVPN config
OPENVPN_USER=username+pmp
OPENVPN_PASSWORD=password
# Wireguard config (example key)
WIREGUARD_PRIVATE_KEY=wOEI9rqqbDwnN8/Bpp22sVz48T71vJ4fYmFWujulwUU=
From: https://www.reddit.com/r/gluetun/comments/1kpbfs2/comment/mswnynw/
π Provider Configurations
Copy the block for your specific provider and paste it into the environment section above. Order is in simple list, not order of preference. I personally use PIA.
Option A: NordVPN (OpenVPN)
Note: While Nord supports WireGuard (NordLynx), extracting the private key is complex. OpenVPN is the easiest setup.
- VPN_SERVICE_PROVIDER=nordvpn
- OPENVPN_USER=service_user
- OPENVPN_PASSWORD=service_passOption B: Private Internet Access (PIA)
PIA works excellently with Gluetun and supports port forwarding automation.
- VPN_SERVICE_PROVIDER=private internet access
- OPENVPN_USER=p1234567
- OPENVPN_PASSWORD=password
- VPN_PORT_FORWARDING=on # Gluetun will automate the port assignment!Option C: Mullvad (WireGuard)
Mullvad is the privacy gold standard and works natively with WireGuard in Gluetun for blazing speeds.
- VPN_SERVICE_PROVIDER=mullvad
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=your_key
- WIREGUARD_ADDRESS=10.xx.xx.xx/32π Step 2: Routing Your "Bad" Traffic
Now for the magic. We generally only route Download Clients (qBittorrent, NZBGet, SABnzbd) or Index Managers (Prowlarr) through the VPN.
Do you need to route Sonarr/Radarr? Generally, no.
- The Traffic is Safe: Sonarr and Radarr communicate with indexers via HTTPS. This means your traffic is encrypted; your ISP can see you are talking to a tracker, but they cannot see what you are searching for or downloading (the
.torrentfile). Mind you this is why I prefer usingmagnetlinks... - The Risk is Low: You aren't transferring the copyrighted content itself through these apps; you're just grabbing metadata.
- The Complexity: Routing them through the VPN can make it harder for them to talk to your download clients if you aren't careful.
Torrenting is the risk. The actual peer-to-peer transfer in qBittorrent is often unencrypted and easily visible to ISPs and copyright trolls. This is what must go through the tunnel.
How to Connect a Container to Gluetun
We use the network_mode directive in Docker. This tells a container: "Don't use your own network card. Plug directly into Gluetun's network card instead."
- Remove Ports: You must remove the
ports:section from your app container (e.g., qBittorrent). Since it doesn't have its own network anymore, it can't open its own ports. - Move Ports: Ensure those Web UI ports (e.g.,
8080for qBit) are listed in the Gluetun container'sports:section instead. - Add Network Mode: Add the line
network_mode: service:gluetun
Example: Routing NZBGet through Gluetun
nzbget:
image: lscr.io/linuxserver/nzbget:latest
container_name: nzbget
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=America/Toronto
volumes:
- /opt/appdata/nzbget:/config
- /data/downloads:/downloads
# OLD WAY:
# ports:
# - 6789:6789
# NEW WAY (The Secure Way):
network_mode: service:gluetun
# This tells Docker: "Don't give this container a network.
# Use Gluetun's network stack instead."
Example: Routing QBittorrent through Gluetun
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
# ... environment and volumes ...
# β OLD WAY (Delete this section):
# ports:
# - 8080:8080
# β
NEW WAY (The Secure Way):
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthyFor more details on various ways to connect dockers in the same compose or in a different compose, read up here:
Putting it all Together - Full Stack
version: "3"
services:
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
ports:
# --- WEB UI PORTS ONLY ---
# We only map ports that YOU need to access from your LAN.
# We do NOT map torrent ports (like 6881) here because
# incoming VPN traffic is handled by the VPN provider, not Docker.
- "8080:8080" # qBittorrent Web UI
- "9696:9696" # Prowlarr Web UI
- "6789:6789" # NZBGet Web UI
volumes:
- /opt/appdata/gluetun:/gluetun
environment:
- TZ=America/Toronto
# --- VPN SETTINGS ---
# See full list of providers here:
# https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers
- VPN_SERVICE_PROVIDER=nordvpn
- VPN_TYPE=openvpn
- OPENVPN_USER=your_service_username
- OPENVPN_PASSWORD=your_service_password
- SERVER_REGIONS=Canada
# --- NETWORK PERMISSIONS (Optional) ---
# Only uncomment this if your containers inside the VPN (e.g. Prowlarr)
# need to talk to services OUTSIDE the VPN (e.g. Sonarr on your LAN).
# - FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24
restart: always
# -------------------------------------------------------
# QBITTORRENT (Attached to Gluetun)
# -------------------------------------------------------
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
container_name: qbittorrent
environment:
- PUID=1000
- PGID=1000
- TZ=America/Toronto
- WEBUI_PORT=8080
volumes:
- /opt/appdata/qbittorrent:/config
- /data/downloads:/downloads
network_mode: service:gluetun
depends_on:
gluetun:
condition: service_healthy
restart: unless-stoppedπ Key Configuration Details
- Port Moving is Mandatory: If you try to keep
ports: 8080:8080inside the qBittorrent service, Docker will throw an error. You are effectively unplugging qBittorrent's network cable and plugging it into Gluetun. Therefore, Gluetun becomes the only door in or out. All Web UI ports must be declared in the Gluetun service. FIREWALL_OUTBOUND_SUBNETS(The Common Myth): You might see guides claiming you need this setting to access the Web UI from your browser. This is false.- Inbound is Automatic: Docker handles the inbound traffic from your browser to the Web UI via the port mapping. You don't need extra firewall rules for this.
- When DO you need it? You only need to define
FIREWALL_OUTBOUND_SUBNETSif the container inside the VPN needs to initiate a connection to something outside on your LAN. - Example: If Prowlarr is inside the VPN, but Sonarr is running normally on your LAN IP (
192.168.1.50), Prowlarr needs this setting to be allowed to "reach out" and talk to Sonarr.
- NordVPN Credentials:
- Do NOT use your regular email/password. NordVPN (and many others) use special "Service Credentials" for manual setups like this.
- Find them in your Nord Account dashboard under Set up NordVPN manually.
- Verification: You don't need complex commands to see if it works. Just check the logs! Run
docker logs gluetun. If successful, you will see a message confirming the connection and displaying the Public IP assigned by the VPN provider. If you see this IP, your traffic is secure.
β‘ WireGuard Setup vs. OpenVPN
The example above uses VPN_TYPE=openvpn because it is easiest to set up with just a username/password. For details on WG in Gluetun, read here:
The definitive HOWTO for setting up ProtonVPN, Gluetun, and Qbittorernt with fully automated port forwarding.
by u/sboger in gluetun
If you want faster speeds with WireGuard, change these lines in the Gluetun section:
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=your_extracted_private_key
# Remove OPENVPN_USER and OPENVPN_PASSWORDNote: Getting the WireGuard private key from NordVPN requires a few extra manual steps (usually running a quick script on a Linux machine), as they don't show it plainly in the dashboard.
Also head over to this thread for the latest details on WG setup which require some work
π΅οΈ Step 3: Verification (The "Is it Working?" Test)
Forget about running complex terminal commands inside containers. Gluetun is smart; it checks its own connection status and tells you the result.
To verify your connection, simply check the container logs:
docker logs gluetun
# Or to see live running log:
docker logs -f gluetunWhat to look for: Scroll to the bottom. If the connection is successful, you will see a message explicitly stating your new Public IP.
INFO [ip getter] Public IP address is 185.xxx.xxx.xxx (Region: Toronto, CA)- If you see an IP that is NOT your home IP: Success! You are secure.
- If you see errors or "unhealthy": The container will likely restart or stop. Because we used
network_mode: service:gluetun, if Gluetun isn't healthy, your apps literally have no network connection. The "Kill Switch" is built-in by design.
π‘οΈ A Note on the "Kill Switch"
You don't need to configure a separate kill switch. By removing the network interface from your app (qBittorrent) and plugging it into Gluetun, you have created a physical dependency.
- Gluetun Up = Internet Access via VPN.
- Gluetun Down = No Network Card = Zero Traffic.
It is physically impossible for qBittorrent to leak traffic over your normal ISP connection because it no longer has access to that hardware interface.
Secure your traffic, protect your privacy, and sail safely. π€π‘οΈ


Member discussion