How to setup HTTPS on qbittorrent WebUI with letsencrypt/certbot?
I am running qbittorrent-nox on an Ubuntu server (16.04).
I use a dynamic DNS address to access the WebUI.
In the github page there is a tutorial to setup HTTPS, but only using self-signed certs.
I have already tried using certbot with the --webroot and --standalone options, but to no avail. One of the main issues is that I can't figure out where the files for the webUI are actually being served from, otherwise --webroot should work.
Can someone please explain how I can make it work?
1 Answer
I managed to do this on Archlinux but I think it would be the same on Ubuntu.
Certificate request
Run certbot like this:
sudo certbot certonly --manual -d yourhostname.orgFollow the instructions. At some point certbot will tell you to run a web server to verify you own the hostname. It will also provide a couple of commands to run a simple web server with python. Run these commands as root in a new shell on your server.
Be sure to forward port 80 from your router to the server hosting qbittorrent-nox and to open the same port on your server firewall. If using ufw, this is the command you want:
sudo ufw allow WWWWWW is a simple configuration to allow connections on port 80.
When the verification procedure is finished, you will find key and certificate files in /etc/letsencrypt/live/yourhostname.org. Copy/paste privkey.pem and cert.pem in your qbittorrent-nox web-ui and you are done.
PS. At the end of this you can kill the python webserver.
PPS. If you are not using port 80 on your server you can also remove it from ufw ALLOW rules and from the port forwarding of your router.
Certificate automatic renewal
Basically you need to automate the steps presented in the previous section.
The command to execute is this:
certbot renew --manual-auth-hook /etc/letsencrypt/scripts/auth-hook.sh \ --manual-cleanup-hook /etc/letsencrypt/scripts/cleanup-hook.sh \ --post-hook /etc/letsencrypt/scripts/post-hook.shThe hooks are basically scripts executed by certbot. certbot will export some environment variables to the scripts. Please refer to certbot docs for more details.
Scripts are placed in /etc/letsencrypt/scripts. Create this folder and place your scripts there or use any other folder of your choice.
Scripts explanation and code:
auth-hook.sh is executed before the verification procedure. It sets up a simple web server to expose the CERTBOT_TOKEN.
#!/bin/zsh
ufw allow WWW
mkdir -p /tmp/certbot/public_html/.well-known/acme-challenge
cd /tmp/certbot/public_html
printf "%s" $CERTBOT_VALIDATION > .well-known/acme-challenge/$CERTBOT_TOKEN
$(command -v python2 || command -v python2.7 || command -v python2.6) -c "import BaseHTTPServer, SimpleHTTPServer; s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); s.serve_forever()" &> /dev/null &cleanup-hook.sh will be executed after the verification procedure no matter if the certificate has been renewed or not. We use it to cleanup the web server.
#!/bin/zsh
kill $(ps aux | grep SimpleHTTPServer | awk 'NR==1{print $2}')
rm -rf /tmp/certbot/public_html/.well-known/acme-challenge
ufw delete allow WWW post-hook.sh will be executed when the certificate is actually renewed. We use it to update /home/user/.config/qBittorrent/qBittorrent.conf.
#!/bin/zsh
systemctl stop qbittorrent.service &&
/etc/letsencrypt/scripts/update_config.py \ --hostname $CERTBOT_DOMAIN \ --configfile /home/user/.config/qBittorrent/qBittorrent.conf &&
systemctl start qbittorrent.service &&Please note: qbittorrent configuration is placed in the home folder of the user running it; edit this script accordingly to your configuration.
update_config.py updates the qBittorrent.conf. I used python because the ConfigParser module was very convenient to edit INI files (key=value). Someone smarter than me can probably achieve the same with sed or awk.
#!/usr/bin/python3
import argparse
import configparser
Config = configparser.ConfigParser()
Config.optionxform = str
parser = argparse.ArgumentParser(description='Updates qbittorrent config.')
parser.add_argument('--hostname', required=True)
parser.add_argument('--configfile', required=True)
args = parser.parse_args()
with open('/etc/letsencrypt/live/' + args.hostname + '/cert.pem', 'r') as f: cert = f.read()
with open('/etc/letsencrypt/live/' + args.hostname + '/privkey.pem', 'r') as f: key = f.read()
cert = cert.replace('\n', '\\n')[:-2]
cert = "\"@ByteArray(" + cert + ")\""
key = key.replace('\n', '\\n')[:-2]
key = "@ByteArray(" + key + ")"
Config.read(args.configfile)
Config["Preferences"]["WebUI\HTTPS\Certificate"] = cert
Config["Preferences"]["WebUI\HTTPS\Key"] = key
with open(args.configfile, 'w') as f: Config.write(f, space_around_delimiters=False)Dry run
Edit these scripts at your needs. Now try to run the previous command with --dry-run option. This will run the renewal procedure even if the certificate has not expired.
certbot renew --manual-auth-hook /etc/letsencrypt/scripts/auth-hook.sh --manual-cleanup-hook /etc/letsencrypt/scripts/cleanup-hook.sh --post-hook /etc/letsencrypt/scripts/post-hook.sh --dry-runCron Job
If everything is fine, you can setup a cron job.
# EDITOR=vim crontab -e
00 04,16 * * * sleep $((RANDOM % 60)); certbot renew --quiet --manual-auth-hook /etc/letsencrypt/scripts/auth-hook.sh --manual-cleanup-hook /etc/letsencrypt/scripts/cleanup-hook.sh --post-hook /etc/letsencrypt/scripts/post-hook.shThe job will run every day at 04:00 and 16:00. The random sleep will select a random minute within the selected hours (as suggested by certbot docs).
We add --quiet option: it is better for cron jobs.