Your VPN Isn't as Invisible as You Think: How My Router Knew I Was Torrenting
I’d been curious about how VPN-tunneled Docker setups actually work in practice — the kind where you route containers through a VPN gateway so all their traffic is encrypted. Every guide tells you the same thing: use network_mode: service:<vpn_container> and all traffic is encrypted, invisible to your local network, end of story.
So I set up a simple experiment: ExpressVPN, qBittorrent, and a Firefox container, all wired together with Docker Compose. I grabbed a few Linux ISOs to generate some real BitTorrent traffic and see how the tunnel behaved.
Then my Unifi Dream Machine flagged BitTorrent traffic on my network.
The Setup
The setup runs on TrueNAS SCALE. The architecture is simple and follows the widely recommended pattern: a single ExpressVPN container acts as the network gateway, and the other containers ride on its network stack.
Here’s the relevant structure (sensitive values redacted):
services:
expressvpn:
cap_add:
- NET_ADMIN
container_name: expressvpn
devices:
- /dev/net/tun
environment:
- CODE=<REDACTED>
- SERVER=ireland
- PROTOCOL=lightwayudp
- ALLOW_LAN=true
- LAN_CIDR=192.168.0.0/23
image: misioslav/expressvpn:latest
ports:
- '30024:30024'
- '51413:51413'
- 51413:51413/udp
privileged: true
restart: unless-stopped
qbittorrent:
container_name: qbittorrent
depends_on:
expressvpn:
condition: service_healthy
environment:
- WEBUI_PORT=30024
- TORRENTING_PORT=51413
image: linuxserver/qbittorrent
network_mode: service:expressvpn
restart: unless-stopped
volumes:
- /mnt/fast-pool/appdata/qbittorrent:/config
- /mnt/fast-pool/Downloads:/downloads
firefox:
container_name: firefox
depends_on:
expressvpn:
condition: service_healthy
image: lscr.io/linuxserver/firefox:latest
network_mode: service:expressvpn
restart: unless-stopped
The key line is network_mode: service:expressvpn. This forces qBittorrent and Firefox to share the VPN container’s network namespace. They have no independent network interface. All their packets go through the VPN tunnel. The port mappings live on the VPN container because it owns the network stack.
This is correct. This is what everyone recommends. And it works — the actual torrent data is going through the VPN.
So what went wrong?
The Suspects
When I saw the Unifi DPI alert, I worked through four possible explanations.
1. DNS Leaks
If DNS queries escape the VPN tunnel and hit your local router, your Unifi gateway sees you resolving tracker domains like tracker.opentrackr.org. The file transfers are encrypted, but the DNS lookups give the game away.
This is a common issue. Whether the VPN container handles DNS internally or falls back to the host’s resolver depends entirely on the image’s implementation. If DNS requests are reaching your Unifi gateway, its DPI engine will happily log them.
2. Traffic Pattern Analysis (DPI Fingerprinting)
Modern DPI doesn’t just read packet contents — it recognizes behavioral patterns. BitTorrent has a distinctive fingerprint even when encrypted: many simultaneous connections to diverse IPs, specific port usage patterns, and characteristic upload/download ratios. Encrypted traffic isn’t invisible traffic; it just can’t be read. Its shape is still visible.
That said, VPN traffic should collapse all of this into a single encrypted stream to one IP (the VPN server). If the tunnel is working, the pattern analysis shouldn’t have enough signal to work with.
3. Leaked Packets During VPN Reconnection
If ExpressVPN drops and reconnects (even briefly), packets can escape unencrypted before the tunnel comes back up. A few seconds of leaked BitTorrent protocol handshakes are enough for DPI to flag it. This is the classic kill-switch problem.
4. The Split Tunnel — The Actual Culprit
Look at this line in the VPN container config:
ALLOW_LAN=true
LAN_CIDR=192.168.0.0/23
This creates a split tunnel. Traffic destined for the local network (192.168.0.0/23) bypasses the VPN entirely and flows directly over the LAN. This is intentional — it’s how you access the qBittorrent WebUI from another machine on your network by hitting http://<server-ip>:30024.
And that’s the problem.
When I open the qBittorrent WebUI from my laptop, the request goes from my laptop to the server over the LAN, unencrypted, and the response comes back the same way. The Unifi gateway sits in the middle of that path. It inspects the traffic, sees qBittorrent’s WebUI protocol and HTTP headers, and flags it as BitTorrent.
The actual peer-to-peer torrent data is going through the VPN. Unifi never sees the file transfers. But it sees the management interface and that’s enough to trigger the DPI classification.
The Distinction That Matters
This is an important nuance that most guides gloss over: there’s a difference between your torrent data being visible and your torrent client being visible.
The split tunnel protects the former but exposes the latter. Your ISP can’t see what you’re downloading. But your local router knows you’re running qBittorrent because you’re accessing its WebUI over the LAN.
For most home users, this is perfectly fine — your router is your own hardware. But if you want to be thorough, or if you’re on a network you don’t fully control, it’s worth understanding.
Mitigations
If the DPI detection bothers you, there are a few options:
Access the WebUI through the VPN, not the LAN. If you’re already running something like Tailscale or Twingate on your network, access the qBittorrent WebUI through that tunnel instead of over the local network. The traffic stays encrypted end-to-end and the Unifi gateway never sees it.
Check for DNS leaks. Exec into the VPN container and verify DNS is resolving through the tunnel:
docker exec -it expressvpn bash
cat /etc/resolv.conf
nslookup example.com
If the nameserver is your local router’s IP, DNS is leaking.
Drop privileged: true. The container only needs cap_add: NET_ADMIN and the /dev/net/tun device. Running in privileged mode gives the container full access to the host’s network interfaces, which is unnecessary and widens the attack surface.
Disable DPI on Unifi. If you own the network and don’t care about the classification, you can just turn off Deep Packet Inspection in your Unifi controller. But understanding why it triggers is more valuable than silencing it.
Takeaway
The Docker network_mode: service:<vpn> pattern works. Your torrent traffic is going through the VPN. But if you enable LAN access for convenience (and most people do), your router can still identify what services you’re running from the unencrypted management traffic. The VPN hides what you’re transferring — it doesn’t hide what you’re running.
Understanding the difference between data privacy and service visibility is what separates a working setup from one you actually understand.