Adding SSL / https to lighttpd

Mon, 04/02/2019 - 10:30 -- James Oakley
Lighttpd

After my earlier post on setting up Lighttpd for simple sites, I thought I'd follow up with how to add SSL / TLS / https to your lighttpd setup. Increasingly, search engines and browsers are encouraging the use of https for all websites, so this is becoming more important. These instructions continue to be for Debian (or suitably similar) flavours of Linux.

Note: Do this in the right order

It's not hard, but you have to do things in the right order. You can give each virtual host its own ssl certificate, using Server Name Indication (SNI for short). Because lighttpd knows that older browsers / operating systems / servers do not support SNI, lighttpd needs to be able to handle the situation when a visitor to an SSL-protected website cannot use SNI. That means it has to have a default SSL certificate that it can use to serve an https page, for any virtual host on the server, before it will start once SSL is enabled.

So, you might think the obvious first step to enabling SSL is to run this command:

lighty-enable-mod ssl

If you do that, the daemon will fail. So we'll get to that command, but there are a few things we need to set up first.

Note: Certificate and Private Key in the same file

With many webservers (like Apache), you often specify three files in your configuration: The private key, the certificate, and the certificate chain (that tells a browser how to get from your certificate to one that it trusts in its root store).

Lighttpd needs to have the private key and the certificate in a single file, one after the other. (The chain still goes in its own file). This will slightly alter the way we generate certificates, or the way you import them from the vendor (if you have purchased an SSL certificate commercially).

The instructions below will use Let's Encrypt, and will concatenate the private key and certificate into a single file.

Step 1: Generate a server-wide certificate

As explained above, we need an SSL certificate that can be used for any virtual host on the server, as a fallback for visits that don't use SNI. If your server is only going to host a single virtual host, you would want to generate this for that virtual host. (That way, browsers that don't support SNI don't generate a warning because the certificate and host names don't match).

I'm assuming that your server is hosting more than one website, so the fallback certificate will use the fully qualified hostname of the server. Say you're running a VPS whose full host name is server.hostname.com - we want a certificate for that hostname.

First, install the Let's Encrypt "certbot" tool that we'll use to generate a certificate:

apt-get install certbot

Then create a bash script to call certbot with the settings you need to generate this server-wide certificate. It's worth doing this, as you can then simply run the script again if you ever want to re-generate or renew this particular certificate. Put your own email address in the email switch. It's a condition of Let's Encrypt that you use a real email when you request a certificate, and they then use it to let you know if a certificate is due to expire and hasn't been renewed. You'll see that the last line concatenates the private key and the certificate.

Put the script where you want: /root or /usr/local/sbin would be good locations:

#!/bin/bash
/usr/bin/certbot certonly \
  --rsa-key-size 4096 \
  --email {your-email@your-domain.com} \
  --renew-by-default \
  --agree-tos \
  --text \
  --webroot \
  --webroot-path /var/www/html \
  --domain $(hostname) 
rm /var/www/html/.well-known -rfv
cat /etc/letsencrypt/live/$(hostname)/privkey.pem /etc/letsencrypt/live/$(hostname)/fullchain.pem > /etc/letsencrypt/live/$(hostname)/fullkeychain.pem

chmod +x your script, and then run it.

Assuming all went well, it there should be a new directory in /etc/letsencrypt/live named server.hostname.com, and in there should be (amongst other things) symbolic links for the most current private key (privkey.pem), certificate (fullchain.pem) and certificate chain (chain.pem). There should also be a file which is the concatenation of the first two of these, which our script named fullkeychain.pem.

Step 2: Secure your SSL

Next, make sure your SSL setup isn't vulnerable to various security flaws (heartbleed, BEAST, etc.). Do your own research. This is somewhat a moving target, as new vulnerabilities are discovered and mitigated from time to time. Your are responsible for ensuring your own server is properly secured. This is just a guide.

When you've finished setting things up, I'd strongly advise using a test suite like the one at SSL Labs to make sure your SSL is set up correctly.

There are two things that need doing. Firstly, we need to generate a Diffie-Hellman parameter that is stronger than 1024-bit. This may take some time, depending on the server's available entropy:

cd /etc/ssl/certs
openssl dhparam -out dhparam.pem 2048

Then, edit the list of ciphers that your server can use by editing /etc/lighttpd/conf-available/10-ssl.conf. You'll edit the cipher-list directive, and add a dh-file directive, so that those two lines read as follows:

ssl.cipher-list = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA "
ssl.dh-file = "/etc/ssl/certs/dhparam.pem"

Step 3: Enable the SSL module, and reload

We now need to tell lighttpd how to find the certificate we generated earlier. Edit /etc/lighttpd/conf-available/10-ssl.conf again, and add the following two lines (replacing {hostname} with the fully qualified hostname for your server, so that it's pointing to the directory identified in step 1 when we generated the certificate).

ssl.pemfile = "/etc/letsencrypt/live/{hostname}/fullkeychain.pem"
ssl.ca-file = "/etc/letsencrypt/live/{hostname}/chain.pem"

Everything should now be lined up. So now we can add the SSL module to lighttpd, and reload the configuration.

lighty-enable-mod ssl
/etc/init.d/lighttpd force-reload 

As detailed last time, check that lighttpd hasn't crashed. You can also put a "hello world" file in /var/www/html and check that you can now view it over https (with your server's name as the host name).

Other Virtual Hosts

Lastly, you'll want to generate certificates for other virtual hosts on your server. Again, do this in the right order. You can't add the SSL lines to your virtual host configuration file until you have a certificate to point to. So:

Create those virtual hosts, as detailed last time, without SSL. That will enable the certbot to generate its validation file in the .well-known directory within the web root. You'll then have a certificate for your domain, which means you can add the filenames to the configuration file.

Let's suppose you've created your virtual host (as detailed last time) without any SSL features. As last time, let's say you've created an account on your server with username 'example', home directory /home/example, and a folder called /home/example/public_html that will hold the files for your website. You've created a file at /etc/lighttpd/vhosts/subdomain.example.com.conf to hold the configuration for this virtual host.

Create a bash script to generate your SSL certificate:

#!/bin/bash
/usr/bin/certbot certonly \
  --rsa-key-size 4096 \
  --email {your-email@your-domain.com} \
  --renew-by-default \
  --agree-tos \
  --text \
  --webroot \
  --webroot-path /home/example/public_html \
  --domain subdomain.example.com \
  --domain www.subdomain.example.com
rm /home/example/public_html/.well-known -rfv
cat /etc/letsencrypt/live/subdomain.example.com/privkey.pem /etc/letsencrypt/live/subdomain.example.com/fullchain.pem > /etc/letsencrypt/live/subdomain.example.com/fullkeychain.pem

Create this where you like. I'd suggest putting it in /home/example, but do note that it needs to run as root (or another user with suitable global permissions to write to /etc/letsencrypt/live).

chmod +x, and run your script. Check that the certificate files are created correctly (just as you did for the server-wide certificate).

You can now edit /etc/lighttpd/vhosts/subdomain.example.com.conf to add these two lines:

ssl.pemfile = "/etc/letsencrypt/live/subdomain.example.com/fullkeychain.pem"
ssl.ca-file = "/etc/letsencrypt/live/subdomain.example.com/chain.pem"

Those line go inside the braces { … } that enclose the configuration section for this virtual host.

Reload lighttpd, and everything should work.

There are two more steps you may wish to carry out

Strict Transport Security

You may wish to enable HTTP Strict Transport Security. This tells compatible browsers that this particular domain should always have SSL enabled, and they should therefore refuse to serve a page on this domain over plain-text http once they've loaded an https page to discover that https should always be used.

(This is to prevent a hacker form hijacking someone's DNS, to point your domain to their server. They'd have a job to issue a genuine SSL certificate for your site, so they'd probably serve that fake site without https. By adding this directive to your website, you prevent this).

This is easily done. Add the following line, or similar, to your virtual host configuration section (again, inside the braced section)

setenv.add-response-header = ( "Strict-Transport-Security" => "max-age=31536000")

Redirect non-https visitors to the https protocol for your domain

Lastly, you may want to send a redirect to any visitors who come to your site over unencrypted http, so that they don't browse without https by mistake.

Again, within the braced section within the configuration file for the virtual host concerned, add the following:

$HTTP["scheme"] == "http" {
  $HTTP["host"] =~ ".*" {
    url.redirect = (".*" => "https://%0$0")
  }
}

Clearly, the $HTTP["host"] condition is redundant. I've included it here, as you may sometimes wish to only put this redirect for specific subdomains or sections of your website, and this shows you where to do this.

Reload, and you're away.

I hope this guide helped. Feel free to add further tips and tricks in the comments.

Blog Category: 

Add new comment

Additional Terms