Renewing your Lets Encrypt/Certbot SSL certificate on nginx with zero downtime

Disclaimer: Specifically the downtime is service nginx restart so its however long your nginx service takes to start, which for me is a fraction of a second.

Lets Encrypt certificate for a Ghost blog, or any nginx application

Already there has been a huge increase of Lets Encrypt/Certbot certificates since its release out of beta. Knocking off the 2 million certificates milestone and then 3 million within 17 days. That's 58823 certificates per day. Absolutely amazing. It just shows how much people care about their security, and how easy it has become to generate, renew, and revoke a SSL certificate.

This site's previous SSL cert was with StartSSL, and I have the up-most respect for what they do. 2 years ago, If you ever wanted a free certificate that was trusted by every browser, that was the place to go. They have a top of the line system and provided a huge service for the community. It took about an hour (or two) to get a certificate signed by them. The waiting time is not for the actual signing process, its authorising the domain name/email address. Certbot (previously Lets Encrypt auto) streamlines this process, down to a few seconds. Lets Encrypt is the new hotness. (Insert obligatory MIB reference)

"Shutup and get to the point" - bender

Generating a cert with near zero downtime

In my instance, I am using Nginx as my webserver with forever to run this ghost blog. A very simple setup. So any request that comes into my domain blog.slowb.ro gets forwarded to ghost.

Generate the Certificate

A quick overview of what you need to do is as follows.

  • Clone or install the Certbot(LetsEncrypt) client
  • Request a new certificate
  • Update your nginx configuration
  • Restart nginx
  • Setup crontab to auto-renew

You never need to stop nginx. Which other tutorials say you need to. A simple restart will allow nginx to load the new certificate changes.

So first we generate our certificate using certonly, as automatically configuring nginx is not supported yet by Certbot (letsencrypt-auto). If you are using nginx, you probably (like this author) don't like when things wrecking havoc in your configurations anyway.

sudo ./certbot-auto certonly -w /var/www/slowbro/blog/ -d blog.slowb.ro        

Ended in an Error code: (as I thought would happen)

Failed authorization procedure. blog.slowb.ro (http-01): urn:acme:error:unauthorized :: The client lacks sufficient authorization :: Invalid response from http://blog.slowb.ro/.well-known/acme-challenge/11123123123123123jlslfjslfjsljfsljf [192.71.245.138]: 404

To fix these errors, please make sure that your domain name was entered correctly and the DNS A record(s) for that domain contain(s) the right IP address.

Now that is clearly a default message. But I know the reason it didn't work

Update Ghost nginx config to allow .well-known

Because I proxy all requests to ghost. Ghost was trying to find a post under https://blog.slowb.ro/.well-known/ which, of course, does not exist. So it was getting a 404 error.

All you have to do is create a location block in your nginx configuration as follows

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

Find what your "root" directive in your nginx configuration, and service nginx reload

Now, re-run the original command, and you should see:

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/blog.slowb.ro/fullchain.pem.

Update Nginx configuration

We have a new certificate for our site but nginx is still not using it. You need to update your virtual host configuration to point to the new certificate.

Add below to your /etc/nginx/sites-enabled/<site>, replacing blog.slowb.ro with your site:

    ssl_certificate /etc/letsencrypt/live/blog.slowb.ro/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.slowb.ro/privkey.pem;

Do a configuration test, and restart!

sudo service nginx configtest && \
sudo service nginx restart

Auto-Renewal

As of v0.4.0 (as of writing it's v0.6.0), certbot-auto has a renew flag, to automatically renew all certificates. Add the following to a cronjob to run monthly and you should always have an up-to-date SSL certificate.

01/09/2016: Updated Cron to use --renew-hook which only runs on a successful renewal of certificates.

$certbot_github_directory/certbot-auto renew --renew-hook "service nginx configtest && service nginx restart"

If you use the certificates elsewhere, you can replace restarting nginx with a deploy script.

Thoughts

If you already have letsencrypt handling all your certificates, then you should be fine in letting it auto-renew. The script letsencrypt-auto returns 0 when it doesn't renew, and their are no errors. So if you ran this weekly, your nginx service would restart weekly. Which is not acceptable for a production system.

Any questions, queries, or things that I'm clearly wrong about, let me know in the comments.

Updated: 15th of May 2016: Letsencrypt client is now Certbot. All hail Certbot!
Updated: 26th of July 2016: Updated NGINX and crontab for new pre-hook and post-hook to restart nginx
Updated: 25th of October 2016: Updated Letsencrypt repo to Certbot repo