Let's Encrypt, the brand new and free Certificate Authority (CA), is now in public beta and I've just switched over to start using their certificates along with auto-renewal. No more re-issuing every year, that's right, it's all auto-magic!


Let's Encrypt Logo


Let's Encrypt!

There are many attractions to using Let's Encrypt (LE) but one of the biggest draws for me was that the process of obtaining certificates can now be fully automated! No more tracking deadlines, no more manually renewing certificates, it's all taken care of! You can read more in the How It Works section of the site which goes into further detail. Another great benefit is being able to roll all of the domains and subdomains I have into the SAN on a single certificate. No more managing multiple certificates! It's worth pointing out now that I'm using NginX on Ubuntu, so that's what this guide is going to focus on. I'm also not using the official LE client to handle my certificate issuing and renewal, I'm using this tiny script called acme-tiny created by Daniel Roesler, so thanks to him for making that! This is mainly because the LE client generates a new key pair at each auto-renew which doesn't work well with HPKP. Now that's all sorted, let's get started!


Add a new user for acme-tiny

We need to add a new user that will control all of our certificate issuing and renewal process to segregate it from everything else. Create the user with a strong password and fill out any of the other details if you wish.

sudo adduser acme

Adding user 'acme' ...
Adding new group 'acme' (1001) ...
Adding new user 'acme' (1001) with group 'acme' ...
Creating home directory '/home/acme' ...
Copying files from '/etc/skel' ...
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Changing the user information for acme
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n]

Once that's sorted you can switch to your new user and navigate to the home directory.

su acme
cd ~

The remainder of the tutorial should be carried out as the acme user unless otherwise stated.


Get a copy of acme_tiny

You need to create a local copy of the script that we're going to use to handle our certificate renewal for us. The script can be found on GitHub here and you need to create a file called acme_tiny.py that contains it.

wget -O - https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py > acme_tiny.py

You should really, and I mean really, stop to take a look through the code here to make sure that nothing malicious is going on. It's less than 200 lines long at the time of writing which means it shouldn't take long at all. This is crucial and I advise that you don't continue until you have audited the code!


Create an account key for Let's Encrypt

LE use key based authentication and because I'm not using the LE client, I need to generate my own key for this. I'm using OpenSSL to generate a new 4096bit RSA key.

openssl genrsa 4096 > account.key

Simple enough, that's sorted. It's also a good idea to backup this key in a safe place!


Create a custom OpenSSL conf

When generating the Certificate Signing Request (CSR) to use with LE, I found myself adding a lot of flags to the command for the subdomains I wanted in the SubjectAltName (SAN) field. To get around that I just create a custom copy of the OpenSSL conf to use in CSR generation for the sake of being tidy and the ease of generating new CSRs down the road. Take a copy of your OpenSSL configuration file.

cp /etc/ssl/openssl.cnf openssl.cnf

Edit your new copy of the file with the following:

nano openssl.cnf

In the file you have to look for the [ req ] section and then find and uncomment the following line. If the line doesn't exist you can simply add it. This ensures that the next section of config changes we make are included.

req_extensions = v3_req

Tip: You can use Ctrl+W to open the search box in nano. Once that's done you need to go to the [ v3_req ] section and add the following line. This indicates that we want to populate the SAN field with the array we're about to create.

subjectAltName = @alt_names

Finally, you need to create the array of names you would like in the SAN. Remember, this includes all domains! You will see I have scotthelme.co.uk and www.scotthelme.co.uk in there. Don't forget to do a similar thing if required. Because all of my "scotthelme" domains are hosted off this server, I'm adding all of them to the SAN so I can manage everything with one certificate from now on, and one renewal. Create this new section at the bottom of the config file and populate it according to your needs.

[ alt_names ]

DNS.1 = www.scotthelme.co.uk 
DNS.2 = scotthelme.co.uk
DNS.3 = strongssl.scotthelme.co.uk
DNS.4 = weakssl.scotthelme.co.uk
DNS.5 = www.scotthelme.com
DNS.6 = scotthelme.com

Generate the new CSR

Now we're all set to generate the CSR that we will use to get LE to sign our new certificates each renewal. (You should need to switch to another user for this section as the acme user shouldn't have access to your private key file)

openssl req -new -key /path/to/your/private.key -out scotthelme.csr -config openssl.cnf

You are about to be asked to enter information that will be incorporated into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:Lancashire
Locality Name (eg, city) []:Clitheroe
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Scott Helme
Organizational Unit Name (eg, section) []:IT
Common Name (e.g. server FQDN or YOUR name) []:scotthelme.co.uk
Email Address []:[email protected]

You don't need to enter a password so just hit enter to pass through the prompts.

Please enter the following 'extra' attributes to be sent with your certificate request
A challenge password []:
An optional company name []:

You can check the CSR with the following command:

openssl req -in scotthelme.csr -noout -text

In the output look for the following sections and check they are correct:

X509v3 Subject Alternative Name:
DNS:www.scotthelme.co.uk, DNS:scotthelme.co.uk, DNS:strongssl.scotthelme.co.uk, DNS:weakssl.scotthelme.co.uk, DNS:www.scotthelme.com, DNS:scotthelme.com

If everything checks out, you're good to continue.


Give acme user permission to restart nginx

We don't want our new user to be too privileged so all we're going to give it permission to do with sudo is control the nginx service. You need to edit the visudo file and setup the permissions which will need to be done by a user with sudo permissions.

sudo visudo

In the editor, find the privilege specification section and add in a new line for the acme user.

# User privilege specification

root    ALL=(ALL:ALL) ALL
acme    ALL=NOPASSWD: /usr/sbin/service nginx *

This line gives the user the ability to run commands such as sudo service nginx reload, which is required to load our new certificates after it has generated them.


Hosting the challenge files

We can prove to LE that we control a domain by hosting challenge files. When we ask for a certificate for a domain, LE will ask us to host a certain file that it can access to prove we have administrative control. Create a new folder in the home folder of the acme user called challenges.

mkdir /home/acme/challenges/

Now the folder is created, we need to instruct NginX to make it available. Edit the vhost file for the domain/s you're going to get certificates for and make sure the folder is available over port 80.

server {
listen 107.170.218.42:80;
listen [2604:a880:1:20::207:b001]:80;
server_name www.scotthelme.co.uk scotthelme.co.uk;

location /.well-known/acme-challenge/ {
    alias /home/acme/challenges/;
    try_files $uri =404;
}

Reload the config so that it's live and you should be all set for NginX to host your challenge files.


Testing out certificate issuance

You're now ready to do a test run and make sure everything works ok! Run the acme_tiny script with the appropriate flags and you should get your first certificate.

python /home/acme/acme_tiny.py --account-key /home/acme/account.key --csr /home/acme/scotthelme.csr --acme-dir /home/acme/challenges > /home/acme/signed.crt

You should see some output similar to this that is specific for your situation.

Parsing account key...parsed!
Parsing CSR...parsed!
Registering account...already registered!
Verifying www.scotthelme.co.uk...verified!
Verifying strongssl.scotthelme.co.uk...verified!
Verifying scotthelme.co.uk...verified!
Verifying scotthelme.com...verified!
Verifying weakssl.scotthelme.co.uk...verified!
Verifying www.scotthelme.com...verified!
Signing certificate...signed!

If everything goes well you will now have a certificate file that can be used with your private key to host on your site! NginX does require the intermediate file to be appended to the leaf certificate so let's sort that out.

wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > chained.pem

UPDATE: 29 Mar 2016, wget updated to use the x3 intermediate after it was changed.


Now that's all sorted, you need to put the new certificate to use. Edit the appropriate vhost file for your site and update the certificate being used.

ssl_certificate /path/to/chained.pem;

Reload the configuration and you should now see your certificate in use.

LE certificate in use


Setting up auto-renew for your certificates

Now that everything is working we need to sort out our auto-renewal so we don't have to worry about doing this every 3 months. We're going to create a script that will handle all of this for us.

nano renew.sh

Add the contents of the script.

#!/bin/bash

python /home/acme/acme_tiny.py --account-key /home/acme/account.key --csr /home/acme/scotthelme.csr --acme-dir /home/acme/challenges > /home/acme/signed.crt || exit
cat /home/acme/signed.crt /home/acme/intermediate.pem > /home/acme/chained.pem
sudo service nginx reload

UPDATE: 20 Dec 2015, added || exit to the first command so that if the renewal fails, we don't overwrite our current certificate chain.


Then save and exit the editor. You need to make the script file executable and it's ready to go!

chmod +x renew.sh

To make sure it gets called every month, add an entry in your crontab.

crontab -e

Once the editor is open add a new line.

0 0 1 * * /home/acme/renew.sh 2>> /var/log/acme_tiny.log

This will tell cron to run the script on the first day of every month and write the output to the log file specified. To create the log file, switch to a user with sudo privileges and create the file then change ownership to the acme user.

sudo touch /var/log/acme_tiny.log
sudo chown acme:acme /var/log/acme_tiny.log

That's it!

That seemed like a slightly longer blog than I was expecting but overall, what we're doing here isn't that complicated. Now everything is setup there is almost no maintenance. After the first of every month you can check to see your site has a freshly generated certificate courtesy of LE and you don't need to worry about renewals any more. Each certificate has a 3 month validity and we're renewing every month, so we're staying way ahead of the expiry. The reason I used the acme_tiny script was to support HPKP so I just wanted to cover a little on that too.


HPKP

As the LE client regenerates the key pair used on each renewal, HPKP is difficult to implement. The acme_tiny script that I'm using has a CSR to use at each renewal, which keeps the same private key in use. This means, if you're pinning at the leaf like I am, that you can continue to renew your certificates without a problem. If you do ever want to renew your private key, perhaps like you would have done previously every year, you can simply move one of your backup keys into production. You re-generate the CSR against the new key and run the script manually to issue a new certificate against the new public key. You can issue 5 certificates for a domain in any 7 day period with LE so there's no problem there. Once that's done, you remove the now outdated pin from your policy and generate another backup key pair as you would have done before to insert into the policy. There are a few points to note about HPKP if you want to pin at the leaf.


  1. If you are pinning at the leaf with HPKP you should generate and keep at least 2 backups. These backups needs to be offline and secure.
  2. You can't consume a backup pin more than once in the time defined by the max-age of your HPKP policy if you only have 2 backups. This is to always ensure you have a backup pin available in the case of a forced renewal like a private key compromise that remains within the cache period of previous visitors.
  3. Please make sure you fully understand HPKP before you try to deploy it!

You can also avoid some of these troubles by pinning at the root level and perhaps pin the LE CA and at least another one as a backup. GitHub currently do this and you can examine their policy here.

Update: 24th July 2016 The Let's Encrypt client, CertBot, supports renewing with a CSR which means HPKP is easy to deploy even with the official client. Look for the --csr flag in the documentation.


If you have any comments or feedback on the article please leave them below!


Update 7th Feb 2016

I wrote a new article on how to approach the renewal process. Rather than renewing at fixed intervals I created a script to check the remaining validity period of the certificate and it will only renew if the certificate has less than 7 days left. This has several benefits that you can read about in the article, Let's Encrypt Smart Renew.