Files
website-hadow.fr/_posts/2025-12-22-qbittorrent-through-a-vpn-with-podman.md
2025-12-22 16:39:11 +01:00

10 KiB
Raw Blame History

layout, author, tags
layout author tags
post Sam Hadow podman sysadmin torrent

In this blog post I'll show you how to run qbittorrent in a podman (in rootless) container and route its traffic through a VPN connection, in this case ProtonVPN.

note: does the VPN provider matter?

The VPN provider, if we don't take into account privacy, doesn't matter much as long as port forwarding is supported. Without port forwarding downloading torrents might still be possible but don't expect a good seeding/leeching ratio.

For BitTorrent, the protocol behind, to work, at least one peer has to be an active node (have a publicly open port) and the goal of port forwarding is to have this publicly open port. With port forwarding enabled you increase the number of peers you can communicate with. And since more peers will be able to initiate a connection with you (passive nodes can always initiate a connection), you'll have a better seeding/leeching ratio.

On less active or older torrents, port forwarding matters even more as if you end up in a situation where one peer has all the files, another peer wants to download them, but both have their ports closed, they won't be able to establish a connection between each-other and the second peer won't be able to download the torrent.

Even with closed ports, peers are still able to discover each other with the trackers, DHT (Distributed Hash Table) or PEX (Peer Exchange). Only the connection establishement part is affected.

note: Why is a VPN prefered when torrenting?

A VPN is prefered when torrenting mainly for privacy and risks reductions.
BitTorrent is not anonymous by design, every peer in the swarm can see your public IP address. By the way it's how copyright monitoring companies are able to identify seeders and send copyright infrigement letters.

A VPN hides your public IP by exposing only the VPN endpoint. It helps not getting identified by copyright monitoring companies, and also against direct attack to your home connection from malicious peers. Without a VPN your IP can be logged accross long time periods and multiple torrents which allows profiling and targeted harassment.

It also encapsulates and encrypts traffic further so BitTorrent usage isnt visible. Obfuscating your traffic might be useful against throtling as some ISPs detects BitTorrent via DPI (deep packet inspection) to throttle it or even throttle any kind of P2P traffic.

But keep in mind that a VPN doesn't guarantee anonimity, the VPN provider can still see (and potentially log) your real IP address as well as the packets going through it.

Disclaimer:

hiding your IP address from copyright monitoring companies obviously doesn't make piracy legal, it just drastically reduces the risks of you getting identified and caught.

Disclaimer 2:

A VPN doesn't protect you against malicious torrent payloads (a trojan disguised as media for example) and attacks over the existing torrent connection (exploit vulnerabilities in the client, sending malformed data and so on...)

Steps

Creating the containers

1. Pod creation

First I created a pod, without an infrastructure --infra=false because we'll use gluetun network and not the host network (which wouldn't go through the VPN connection). A pod is not required, you could just create the qbittorrent and the gluetun container. But I prefered to create one to logically group the containers and also see them with the command podman pod ls.

The systemd unit file will look like this:

# pod-qbittorrent.service

[Unit]
Description=Podman pod-qbittorrent.service
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/run/user/1000/containers
Wants=container-torrent-qbit.service container-torrent-gluetun.service

[Service]
Type=oneshot
RemainAfterExit=yes
Environment=PODMAN_SYSTEMD_UNIT=%n
TimeoutStartSec=30
TimeoutStopSec=30

ExecStart=/usr/bin/podman pod create \
	--pod-id-file %t/pod-qbittorrent.pod-id \
	--exit-policy=stop \
	--name qbittorrent \
	--infra=false \
	--replace

ExecStop=/usr/bin/podman pod rm -f --pod-id-file %t/pod-qbittorrent.pod-id

[Install]
WantedBy=multi-user.target

2. Gluetun container

We will use gluetun for the VPN connection.

First you need to create the secrets for WIREGUARD_ENDPOINT_IP, WIREGUARD_PUBLIC_KEY, WIREGUARD_PRIVATE_KEY. Creating these secrets is not necessary but it avoids exposing sensitive information in your configuration files.

With ProtonVPN when downloading a wireguard configuration it'll look like this:

[Interface]
PrivateKey = <private_key>
Address = 10.2.0.2/32
DNS = 10.2.0.1

[Peer]
# BE#67
PublicKey = <peer_public_key>
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = <endpoint_IP>:51820

To create the secrets you'll need to first derive the public key from the private key:

echo -n "private_key" > privkey
wg pubkey < privkey

Then you can create the secrets

echo -n "private_key" > /tmp/secret
podman secret create wg_sk /tmp/secret
echo -n "public_key" > /tmp/secret
podman secret create wg_pk /tmp/secret
echo -n "endpoint_IP" > /tmp/secret
podman secret create wg_ip /tmp/secret

Then the container unit file will look like this:
Of course you'll need to adapt the path for the volume

# container-torrent-gluetun.service

[Unit]
Description=Podman container-torrent-gluetun.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-qbittorrent.service
After=pod-qbittorrent.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=90
ExecStart=/usr/bin/podman run \
	--cidfile=%t/%n.ctr-id \
	--cgroups=no-conmon \
	--rm \
	--pod-id-file %t/pod-qbittorrent.pod-id \
	--sdnotify=conmon \
	--replace \
	-d \
	--name=torrent-gluetun \
	-p 8080:8080 \
	--stop-timeout 60 \
	--tmpfs /tmp \
	--cap-add NET_ADMIN \
	--cap-add NET_RAW \
	--device /dev/net/tun:/dev/net/tun \
	-v /home/data/podman/gluetun:/gluetun:z \
	-e VPN_SERVICE_PROVIDER=custom \
	-e VPN_TYPE=wireguard \
	--secret wg_ip,type=env,target=WIREGUARD_ENDPOINT_IP \
	-e WIREGUARD_ENDPOINT_PORT=51820 \
	--secret wg_pk,type=env,target=WIREGUARD_PUBLIC_KEY \
	--secret wg_sk,type=env,target=WIREGUARD_PRIVATE_KEY \
	-e WIREGUARD_ADDRESSES=10.2.0.2/32 \
	-e VPN_PORT_FORWARDING_PROVIDER=protonvpn \
	-e VPN_PORT_FORWARDING=on \
	--label io.containers.autoupdate=registry \
	docker.io/qmcgaw/gluetun:latest
ExecStop=/usr/bin/podman stop \
	--ignore -t 30 \
	--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
	-f \
	--ignore \
	--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all

[Install]
WantedBy=default.target

Here I put "custom" in the VPN_SERVICE_PROVIDER environment variable instead of "protonvpn", or whichever VPN provider you might be using, because port forwarding wouldn't work otherwise. "protonvpn is then later specified in theVPN_PORT_FORWARDING_PROVIDER environment variable.
You can have a look at the wiki in the option section for port forwarding if you use another VPN provider.

When looking at the logs from gluetun container with the command:

podman logs torrent-gluetun

you should see a line like this:

INFO [port forwarding] port forwarded is 45497

It's the port you'll want to use in qbittorrent. However check it again when you restart the container as this port can sometimes change.

3. Qbittorrent container

After the gluetun container, we can create the container for qbittorrent, we'll make this container bind to gluetun container in order to use the network provided by it and correctly route the traffic through the VPN.
Don't forget to adapt QBT_WEBUI_PORT to the one you prefer

# container-torrent-qbit.service

[Unit]
Description=Podman container-torrent-qbit.service
Wants=network-online.target container-torrent-gluetun.service
After=network-online.target container-torrent-gluetun.service
RequiresMountsFor=%t/containers
BindsTo=pod-qbittorrent.service container-torrent-gluetun.service
After=pod-qbittorrent.service

[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=1860
ExecStart=/usr/bin/podman run \
	--cidfile=%t/%n.ctr-id \
	--cgroups=no-conmon \
	--sysctl net.ipv6.conf.all.disable_ipv6=1 \
	--rm \
	--pod-id-file %t/pod-qbittorrent.pod-id \
	--sdnotify=conmon \
	--replace \
	-d \
	--name=torrent-qbit \
	--network container:torrent-gluetun \
	--stop-timeout 1800 \
	--tmpfs /tmp \
	-e QBT_LEGAL_NOTICE=confirm \
	-e QBT_WEBUI_PORT=8080 \
	-v /home/data/podman/qbittorrent/config:/config:z \
	-v /home/data/podman/qbittorrent/data:/downloads:z \
	--label io.containers.autoupdate=registry docker.io/qbittorrentofficial/qbittorrent-nox:latest
ExecStop=/usr/bin/podman stop \
	--ignore -t 1800 \
	--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
	-f \
	--ignore -t 1800 \
	--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all

[Install]
WantedBy=default.target

managing the containers

All my unit files are in ~/.config/systemd/user

When you create new unit files, or change them, don't forget to run

systemctl --user daemon-reload

Then you can start the pod like this:

systemctl --user start pod-qbittorrent.service

(or start it and enable it with enable --now instead of start)

And to stop it:

systemctl --user stop pod-qbittorrent.service

In case qbittorrent doesn't get an external IP because it started before the VPN connection was established you can restart it like this

systemctl --user restart container-torrent-qbit.service

note:

You might have noticed that I use the option :z on my containers, this is because I use SELinux. You can read my post about SELinux here for more details.

note 2:

You might also have noticed I use a label on my containers:

--label io.containers.autoupdate=registry

This label is used to automatically pull the latest image and update the container whenever a new latest image is released. It is not necessary for this setup to work but is useful to have your containers automatically updated.