[HowTo] Nginx reverse proxy for WebUI with letsencrypt for HTTPS

Last updated: 2019-01-11
Added basic authentication to protect against the path traversal bug mentioned below.

  • I assume you remotely know what you’re doing;
  • All commands are run as root;
  • You have an A-record pointing to your IP (out of scope for this HowTo);
  • You need ports 80 and 443 open on your router and pointing to OSMC (port 80 is mandatory for certificate renewal, and 443 eventually as well due to the configuration).

Warning: Disable port forwarding on port 443 when on Kodi v17 (current OSMC stable). Your certificate will still renew due to the nginx configuration. This is due to the following vulnerabilities:
Once on Kodi v18, enable port-forwarding at your own risk, as the developers of Kodi say the WebUI should not be public-facing.


  • Protect your login information;
  • Access the WebUI securely from outside your network.

Lets start off by installing the necessary packages, all available in the regular repositories. We’ll also stop nginx as it starts automatically.

apt install nginx-extras certbot apache2-utils
systemctl stop nginx

The following will generate a new certificate. Make sure to replace the -d option with your own domain. You’ll be asked for an e-mail address for renewal reminders.

certbot certonly --rsa-key-size 4096 --standalone -d yourdomain.tld

This is a cronjob for automatically checking and renewing your certificate.

cat << "EOF" > /etc/cron.daily/certbot-renew

certbot renew --rsa-key-size 4096 --webroot -w /var/www/letsencrypt --post-hook "systemctl reload nginx" 2>&1
chmod +x /etc/cron.daily/certbot-renew
mkdir -p /var/www/letsencrypt
chown -Rf www-data: /var/www/letsencrypt

You can test this later, once nginx is running, with this command:

certbot renew --rsa-key-size 4096 --webroot -w /var/www/letsencrypt --dry-run

Lets put all the log files in the right place with the right permissions and generate a dhparam file. We also remove the default nginx site.

mkdir -p /var/log/nginx/yourdomain.tld
touch /var/log/nginx/yourdomain.tld/{access.log,error.log}
chown -Rf www-data: /var/log/nginx/
openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096
rm /etc/nginx/sites-enabled/default

Add the following into your /etc/nginx/nginx.conf, somewhere in the logging section, for the logging:

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

Next up are snippets that can be used for future configurations. Be sure to edit the file pertaining to your domain.

cat << "EOF" > /etc/nginx/snippets/gzip.conf
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 5;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE 1-6\.";
gzip_types text/plain text/css text/xml application/javascript application/atom+xml application/rss+xml image/svg+xml image/x-ms-bmp application/json;

cat << "EOF" > /etc/nginx/snippets/location-letsencrypt.conf
location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root /var/www/letsencrypt;

cat << "EOF" > /etc/nginx/snippets/ssl-params.conf
# from https://cipherli.st/
# and https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve "secp521r1:secp384r1";
ssl_buffer_size 8k;
ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver valid=300s;
resolver_timeout 5s;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none; 
ssl_dhparam /etc/ssl/certs/dhparam.pem;

cat << "EOF" > /etc/nginx/snippets/ssl-yourdomain.tld.conf
ssl_certificate         /etc/letsencrypt/live/yourdomain.tld/fullchain.pem;
ssl_certificate_key	    /etc/letsencrypt/live/yourdomain.tld/privkey.pem;
ssl_trusted_certificate	/etc/letsencrypt/live/yourdomain.tld/fullchain.pem;

Set a username and password using the htpasswd tool in apache2-utils for logging into Chorus. This will protect you from the path traversal bug in Kodi v17. Change the username accordingly.

htpasswd /etc/nginx/.htpasswd USERNAME

Finally, the actual config file for the WebUI proxying. Don’t forget to adjust according to your DNS record.

cat << "EOF" > /etc/nginx/sites-available/yourdomain.tld
upstream kodi {
    keepalive   512;

server {
    listen	80;
    server_name	yourdomain.tld;
    include	snippets/location-letsencrypt.conf;
    location / {
        return	301 https://$server_name$request_uri;

server {
    listen	443 ssl http2;
    server_name	yourdomain.tld;

    include	snippets/gzip.conf;
    include	snippets/ssl-params.conf;
    include	snippets/ssl-yourdomain.tld.conf;
    include	snippets/location-letsencrypt.conf;

    access_log	/var/log/nginx/yourdomain.tld/access.log main;
    error_log	/var/log/nginx/yourdomain.tld/error.log warn;

    location / {
		auth_basic           "Reserved";
		auth_basic_user_file .htpasswd;

		proxy_set_header   Host $http_host;
    	proxy_set_header   X-Real-IP $remote_addr;
    	proxy_set_header   X-Scheme $scheme;
    	proxy_pass         http://kodi$request_uri;
    	proxy_http_version 1.1;
    	proxy_set_header   Upgrade $http_upgrade;
    	proxy_set_header   Connection "upgrade";

	# _Shouldn't_ need this if you turned on proxy headers? (Settings -> Web interface -> Reverse proxy support)
	location ~ ^/(image|jsonrpc) {
        proxy_pass         http://kodi;
        proxy_http_version 1.1;
    	proxy_set_header   Upgrade $http_upgrade;
    	proxy_set_header   Connection "upgrade";
ln -sr /etc/nginx/sites-available/yourdomain.tld /etc/nginx/sites-enabled/yourdomain.tld

If you’ve already set a username and password for the Chorus within Kodi, you can configure Nginx to authenticate automatically. First, you need to convert a “USERNAME:PASSWORD” string into base64.

echo -n "USERNAME:PASSWORD" | openssl base64

Then paste the output into an nginx proxy_set_header in the /etc/nginx/sites-available/yourdomain.tld. Here’s an example.

        proxy_set_header   Authorization "Basic VVNFUk5BTUU6UEFTU1dPUkQ=";

You can test the configuration with nginx -t and if all is well, start nginx.

nginx -t
systemctl start nginx && systemctl enable nginx

From here on you can protect it further with Fail2ban reading your nginx logs, for instance.

1 Like

Just so you know: I don’t recommend doing this for Kodi v17 where there are known path traversal vulnerabilities.

@sam_nazarko WIll it be safe for Kodi v18?
I’ll recommend port forwarding be disabled until v18 is out.

The Kodi team still state that the web server shouldn’t be public facing.

That’s unfortunate. That’s barely an excuse to ignore such a security vulnerability.

The vulnerability has been fixed in Kodi v18; but there may be other issues with making this public facing.

OK, I’ll edit the information accordingly.

EDIT: I’ve updated accordingly and changed the nginx configuration so that the certificate is still renewable over port 80 and port 443 is disabled externally.