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?

up vote 3 down vote accepted

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.org

Follow 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 WWW

WWW 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.sh

The 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.


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.


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.


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.


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["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-run

Cron 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.sh

The 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.

  • Thanks, this seems to have worked. One final question: does the auto-renewal work? Do I need the python webserver on port 80 for it to work? – Frank Jun 25 '17 at 9:47
  • 1
    Probably. The verification system should be the same, but I have not tried the renewal. I'll do it when I have time and I'll let you know. – Matteo Pergolesi Jun 26 '17 at 11:21
  • Ok, I will wait for your results then. Meanwhile everything seems to be working fine, so I am going to accept your answer. Thank you very much! – Frank Jun 26 '17 at 15:09
  • 1
    @Frank I updated my answer with scripts and information to setup the automatic renewal. The scripts will also update qbittorrent configuration. Hope it helps :) – Matteo Pergolesi Jul 2 '17 at 8:42
  • Ok, have you tested the automatic renewal yourself? This whole post would be a great addition to qBittorrent's wiki on Github, if you have the time to contact the maintainers. Great job! – Frank Jul 3 '17 at 8:03

Your Answer


By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.