SSL all the things with Let's Encrypt and nginx

With the stories around the 5 eyes snooping of internet traffic from the likes of Edward Snowden[1], its hard to justify not using SSL for anything which is remotely sensitive these days.

Now, I don't think the content of this blog it at all sensitive, but all the same, it's good to know how to setup SSL/HTTPS on my server, so when I need it, I can.

My setup for this is:

  • nginx as the server running on a linux VM at Linode.
  • I run Ghost as my blog engine
  • My wife has a Wordpress site, and she's about to deploy a new version which includes an commerce component using WooCommerce. Even tho we will never (ever) see a credit card number in there, it's always good to have the checkout secured.

Using Lets Encrypt to get certificates

This used to be the expensive bit, but thanks to the likes of StartSSL, it's now free for a basic SSL certificate[1:1]. Let's Encrypt moved it even further into the "wow, that was easy" realm.

It's a LOT more for an EV certificate where they certify who you are - the kind we have at work. But ANY properly configured SSL certificate will secure the connection - the extra money is paid for validation, end user trust and the big green box in the address bar. All 2048-bit certificates - which is the normal minimum - are the same otherwise.

First, you need to get a Let's Encrypt. That's as easy as making sure you have git and bc (via sudo apt-get install git bc), then cloning the Let's Encrypt repo into a system-controlled area:

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

Once you have letsencrypt cloned, you need to stop nginx (as it uses port 80) with sudo service nginx stop, and run the letsencrypt client (as root, so sudo su - first or prefix all of these with sudo):

cd /opt/letsencrypt
./letsencrypt-auto certonly --standalone

This will use apt-get to install a few extra bits, possibly moan about some stuff (I ignored it), then ask you for your contact email address (for when certificates expire) and the domain(s) you want to secure.

It's advisable to make a certificate for each site, tho you could generate one for all your sites. I put fastchicken.co.nz,www.fastchicken.co.nz as my domain, and it generated a set of certificates and the the private key in the (root accessible) /etc/letsencrypt folder

in the examples below it's using yourdomain.co,www.yourdomain.co

Remember to restart nginx using sudo service nginx start when you are done.

So, thats the normally hard part done! Repeat it again if you have more domains to configure. Note the folder that this puts the files, as you'll need it later (mine was /etc/letsencrypt/live/<domain>/)

Setting up nginx

nginx is a nice modern web server, and as such, it configures itself using json, or a variant of it, rather than xml or something else. It's pretty sweet to setup, too.

I already have a working configuration listening on port 80, so all I had to do was modify this to work with port 443 (HTTPS) and also to use the generated certificates and keys. Most of the config for this is in the domain-specific file in /etc/nginx/sites-available/yourdomain.co.

you can merge the blocks below if you want to listen on http and https on the same server, and ignore the redirect-specific ones.

The first server block I already had. It used to strip the www off the front, so I made it go to http__s__ not http, as it did before.

server {
    server_name  www.youdomain.co;
    rewrite ^(.*) https://yourdomain.co$1 permanent;
}

The second one is for the HTTP server - this is just a basic rewrite which sends everyone to the https server:

server {
        listen 80;
        listen [::]:80;

        server_name yourdomain.co;
        ## redirect http to https ##
        rewrite ^ https://$server_name$request_uri? permanent;
}

The final one is the HTTPS listener, which is the one which does all the work. I've stripped out a lot of the config here, and just left the HTTPS-specific bits. The rest was unchanged, based on my previous HTTP server config.

server {

        listen 443;
        listen [::]:443;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/yourdomain.co/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/youdomains.co/privkey.pem;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

        ssl_ciphers '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_prefer_server_ciphers on;
        ssl_dhparam /etc/nginx/dhparams.pem;

# This is all the normal nginx stuff
        root etc etc etc
        index index.html index.htm;
        ...
}

The first bit of config here is for Lets Encrypt and general SSL setup:

server {

        listen 443;
        listen [::]:443;

        ssl on;
        ssl_certificate /etc/letsencrypt/live/yourdomain.co/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/youdomains.co/privkey.pem;

This tells the server what to listen on, to turn SSL on, and which certificate and private key to use. The names fullchain.pem and privkey.pem are the standard Let's Encrypt names.

The second bit is something I had when I was ensuring that one of my API's was using proper TLS1.2:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

        ssl_ciphers '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_prefer_server_ciphers on;
        ssl_dhparam /etc/nginx/dhparams.pem;

This tells the server which ssl protocols to use (NO SSLv3 please, it's useless!), which cyphers to use (no insecure cyphers please!) and to favour the server configured cyphers over client provided ones.

Once you do that, you can use SSL Labs Excellent SSL Checker to make sure it's all setup right. I get an A and 90% rating with this configuration.


There is no excuses these days for NOT using SSL, even if you don't think what you are doing is important or needs to be secured. Just do it for the experience, if nothing else.

I took my setup from the excellent Digital Ocean setup guide, and a previous tutorial on how to do SSL / TLS properly - that was a while ago, so no URL.


  1. who's about as close to a personal hero as I can think of. ↩︎ ↩︎