I needed a way to access Deluge’s web UI remotely without opening another port to the world. The answer is the same as it always is: stick Nginx in front of it.
This took me longer than I’d like to admit, mostly because Deluge’s web UI has some quirks around base paths that aren’t obvious from the docs.
What we’re working with
- Debian 12
- Deluge 2.1.1 (the
delugeddaemon +deluge-web) - Nginx already handling SSL via Let’s Encrypt
- Goal:
https://torrent.yourdomain.com→ Deluge web UI
Installing Deluge
If you haven’t already:
apt install deluged deluge-web
Enable and start the services:
systemctl enable --now deluged
systemctl enable --now deluge-web
By default deluge-web listens on 127.0.0.1:8112. Confirm it’s up:
ss -tlnp | grep 8112
The Nginx config
Here’s the block that actually works. The proxy_pass trailing slash and the sub_filter lines are the parts that tripped me up:
server {
listen 443 ssl;
server_name torrent.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/torrent.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/torrent.yourdomain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8112/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Note the trailing slash on proxy_pass — without it, Deluge’s static assets 404.
Locking it down with HTTP basic auth
Deluge has its own password, but adding a second layer at Nginx is trivial and stops bots from even reaching the login page:
apt install apache2-utils
htpasswd -c /etc/nginx/.htpasswd yourusername
Then add to the location block:
auth_basic "Deluge";
auth_basic_user_file /etc/nginx/.htpasswd;
Reload and test
nginx -t && systemctl reload nginx
Hit the URL, enter your basic-auth credentials, then the Deluge password (default: deluge). Change that immediately.
What didn’t work
I first tried running Deluge under a subdirectory (/deluge/) instead of a subdomain. Don’t. Deluge’s web UI hardcodes some absolute paths internally and it breaks in non-obvious ways — assets load, but WebSocket connections fail silently and the UI appears stuck on “connecting”.
Subdomain is the path of least resistance here.