Setting up https with Let’s Encrypt on Nginx

ssltest_rating.png

Let’s Encrypt is an awesome free, automated and open way of protecting your site with https. As you may have noticed this site is using Let’s Encrypt certificate and I’ve started rolling it out to all my other sites too. With free https certificate there’s really no excuse not to use https only. In fact if you want to take advantage of HTTP/2 you’ll need https since no one currently supports it unencrypted even though the spec doesn’t mandate it.

Even if your site doesn’t have any sensitive information if you ever update/login to it from from a untrusted location such as Café your login credential might get disclosed to someone malicious and like most of us you’ll probably use the same credentials in multiple places that might be a real bad thing. Now I didn’t come up with all these steps I’m about to explain here but the credit rather goes to Bjørn Johansen whose blog posts I’ll summarise here. I’ll link all the posts I used as initial reference to set this up on my server at the end of the post in case you’ll need more details. Let’s Encrypt support for Nginx is still experimental and buggy so you’ll need to use manual method to install it.

Setting up Let’s Encrypt client

We’ll use git to get the client and bc is needed later so in Ubuntu/Debian you’ll install them with apt.

apt-get install git bc

Now with git we’ll clone the Let’s Encrypt client repo.

git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

Preparing Nginx

To verify the domain Let’s Encrypt verification server will look for verification files created by the client in a subdirectory of your webroot under:  /.well-know/acme-challenge/

Since I have lots of sites under the same Nginx and I want them all to use https eventually I’ve created a configuration snippet under /etc/nginx/global named letsencrypt-challenge.conf with the following content:

# Allow access to the ACME Challenge for Let’s Encrypt
location ~ /.well-known/acme-challenge {
    allow all;
}

This is not required if you don’t block files starting with a dot.

The server section for the site could look something like this:

server {
    listen 80;
    server_name example.com;
    root /var/www/example.com;

    include global/letsencrypt-challenge.conf;
}

Once you’ve added the global/letsencrypt-challenge.conf in don’t forget to reload your nginx.

service nginx reload

Get the certificate from Let’s Encrypt

Now you are ready to use the Let’s Encrypt client to request a certificate for your domain.

/opt/letsencrypt/letsencrypt-auto certonly --agree-tos --webroot -w /var/www/example.com 
-d example.com

If all goes well you’ll get four files under /etc/letsencrypt/live/example.com. Those files are privkey.pem, cert.pem, chain.pem and fullchain.pem. You’ll need those to setup ssl in Nginx but before we do that let’s make sure the certificate is automatically renewed because it will be valid only 90 days.

Setup auto renew for certificate

So like I just mentioned the certificates from Let’s Encrypt are only valid for 90 days and I’m sure you don’t want to try to remember to renew them manually so we’ll setup a cron job to do that automatically for us. There’s already a nice script that will do all the heavy lifting for us. Well just need to download it and make it executable for root.

curl https://gist.githubusercontent.com/bjornjohansen/aaf0d29f225ffd1ea222/raw/e1b4bec81d32dba86e2d4e9d70a2b9f4d6cca773/le-renew.sh > /opt/le-renew.sh
chown root:root /opt/le-renew.sh
chmod 0500 /opt/le-renew.sh

Please note that this script assumes you installed Let’s Encrypt client in /opt/letsencrypt if you didn’t please adjust the path in the script. It’s also good idea to try to understand what the script does and not just blindly execute any script you’ve downloaded from the web as root.

The script tries to renew the certificate for you when the expiration date is less than 30 days away. Well setup cron to run the script once a week so even if it fails for some reason there’s still plenty of time to get it right. Create a file /etc/cron.d/letsencrypt-renew with following content:

32 5 * * 1 root /opt/le-renew.sh example.com /var/www/example.com > /dev/null 2>&1

Setup https in Nginx

In order to https you’ll need a new server block that listens the port 443 and you’ll need to tell nginx where the private key and certificate are found for this domain.

server {
    listen 443 ssl;
    server_name example.com;
    root /var/www/example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include global/letsencrypt-challenge.conf;
}

That is what is required at the minimum but we are not going to stop there as there are six more steps we can take to make it more secure and optimize it’s https performance.

1) Connection credential caching

Most of the https overhead is in the initial connection setup and by caching the parameters we’ll significantly improve subsequent requests. All you need is following lines in your config:

ssl_session_cache shared:SSL:20m;
ssl_session_timeout 60m;

This creates a shared cache between all the worker processes. 1MB cache can store around 4000 sessions so this should be plenty for most sites. You can adjust it smaller if you are concerned but Nginx should be smart enough not to consume all memory just for the cache.

2) Disable SSL

This may seem counterintuitive but https is actually SSL (secure socket layer) and TLS (transport layer security). Technically SSL has been superseded by TLS and SSL shouldn’t be used because of many weaknesses it has. Disabling SSL means you are making your site not accessible by IE6 but do you really care about that.

The latest version of TLS is 1.2 but there are still modern browsers that only support 1.0 so we should also support it. Just add following line to you nginx config:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

3) Optimize cipher suites

Encryption is at the core of https and some of the ciphers are more secure and some are not secure at all anymore so we’ll want to tell the client the preferred order of cipher suites to use. All of the ciphers on this list use forward secrecy but with this list you’ll loose support for all IE versions on Windows XP but again do you really care.

ssl_prefer_server_ciphers on;
ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5

4) Generate DH parameters

DH parameters affect the Diffie-Hellman key exchange which where client and server negotiate the key for the session. By default it’s only 1024 bit key and our Let’s Encrypt key is 2048 bits so we need to make Nginx also use 2048 bits for DH key exchange otherwise it’s not as secure as it could be. The only downside is that Java 6 doesn’t support anything over 1024 but again do you really care about that.

Generate the DH parameters file with 2048 bit long prime.

openssl dhparam 2048 -out /etc/nginx/cert/dhparam.pem

Add the dhparam to your config file:

ssl_dhparam /etc/nginx/cert/dhparam.pem;

5) Enable OCSP stapling

When a proper browser is presented with a certificate it will check to see if that certificate is revoked from the issuer and that adds extra overhead. This is where Online Certificate Status Protocol (OCSP) comes to rescue. The web server contacts the certificate authority’s OCSP server at regular interval to get a signed response which it then staples on the handshake when the connection is setup. This is much more efficient than having the browser go out to do the check.

To make sure the response from the CA is not tampered with nginx needs to check the CA root and intermediate certificates. Let’s Encrypt client already provides us with all the required certificates so all we need to do is configure stapling and the ssl_trusted_certificate.

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

The resolver is a must and you can use Google public DNS servers as I’ve used here or you can use your own.

6) Strict transport security

HTTP Strict Transport Security (HSTS) is a way to tell the browser that this domain should only be used over https. Even though you might setup redirection from http to https any requests that go over http are insecure. This feature is supported in all modern browsers and it’s really simple to enable you’ll just add a header Strict-Transport-Security with the maximum age. Then for the specified amount of time the browser doesn’t even try to access the site via http.

add_header Strict-Transport-Security "max-age=31536000" always;

Putting it all together

That’s a lot of configuration so here is a complete example configuration:

server {
    listen 443 ssl;
    server_name example.com;
    root /var/www/example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    include global/letsencrypt-challenge.conf;

    ssl_session_cache shared:SSL:20m;
    ssl_session_timeout 60m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5

    ssl_dhparam /etc/nginx/cert/dhparam.pem;

    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    add_header Strict-Transport-Security "max-age=31536000" always;
}

Optional steps

What you most likely want to do is redirect from http to https. That is done by replacing your old server with following:

server {
    listen 80;
    server_name example.com;
    root /var/www/example.com;

    return 301 https://$host$request_uri;
}

Since you have https setup you might want to enable HTTP/2 if you are using new enough Nginx. That is very simple you just add the word http2 after ssl in the listen like this:

listen 443 ssl http2;

But if you are running an older nginx you can still enable SPDY which has been superseded by HTTP/2 but it might still be useful until you can enable HTTP/2. SPDY is enabled similarly to HTTP/2.

listen 443 ssl spdy;

Test your configuration

So how do you know you configured everything correctly. The site might be working in your browser but that still doesn’t guarantee everything is correct. Qualys SSL Labs provides a nice scanner to test your setup. If you configured everything correctly you should get A+ rating just as is shown below for this site.

References:
[1] Let’s Encrypt for Nginx
[2] Optimizing HTTPS on Nginx