Letsencrypt for Free & Easy SSL Certificates

Jun 03, 2016

Length: 13:10

See how to easy it is to use letsencrypt to create and automatically renew FREE SSL certificates!

Command Rundown

The environment:

Here's what we're using in the video

I had a real domain pointing to this server: tutorial.serverops.io.

Initial Setup:

I ran the following just to get the server updated and nginx installed:

sudo apt update
sudo apt upgrade -y
sudo apt install -y nginx

When I went to http://tutorial.serverops.io in the browser, I could then see the default nginx site. Yay!

Nginx Security

Many people protect dot-files in your Nginx server configuration. This means access to any file or folder proceeded with a dot returns an access denied response. This rule in Nginx often looks like this:

location ~* (?:^|/)\. {
    deny all;

This protects url's such as: http://example.com/.git/... - which is good - that's likely not something you want exposed to the public internet!

LetsEncrypt, however, uses a folder named .well-known to validate that you have access to the domain you're attempting to create a certificate for. To allow the use of this directory while still protecting system directories, use the following two Nginx rules:

location ~* (?:^|/)\.well-known {
    allow all;

location ~* (?:^|/)\. {
    deny all;

Update: April 6, 2017 - Using Nginx 1.10.3 I had to use the following configuration to accomplish the above (Github issue reference):

location ^~ /.well-known/acme-challenge/ {
    allow all;

location ~* (?:^|/)\. {
    allow all;

Install Letsencrypt

Letsencrypt used to have you install a command line tool called, appropriately, "letsencrypt". It's since changed to the simpler "certbot".

Ubuntu 16.04 has a package for "letsencrypt" (currently for version 0.4.1-1):

$ apt show certbot # No results
$ apt show letsencrypt
Package: letsencrypt
Version: 0.4.1-1
Priority: optional
Section: universe/web
Source: python-letsencrypt
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Debian Lets Encrypt <letsencrypt-devel@lists.alioth.debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 24.6 kB
Depends: dialog, python-letsencrypt (= 0.4.1-1), python:any (>= 2.7~)
Suggests: python-letsencrypt-apache, python-letsencrypt-doc
Homepage: https://letsencrypt.org/
Download-Size: 10.9 kB
APT-Sources: http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial/universe amd64 Packages
Description: Lets Encrypt main client...

The latest certbot release as of this writing if 0.7.0. Let's not settle for an old version!

This was actually 0.8.0 the next day - development moves fast on it!

To install a newer version:

sudo apt install -y git
cd /opt
sudo git clone https://github.com/certbot/certbot
cd certbot
./certbot-auto -h

We'll use the certbot-auto command.

Install a Certificate

Letsencrypt has a few "modules" which basically boils down to "how do I setup an SSL certificate for you".

In all cases, letsencrypt needs to be able to ping your server over HTTP to confirm that your domain points to the server you're installing the certificate on.


One way letsencrypt does this is with the "standalone" module, which spins up a web server listening on port 80. The issue there may or may not be obvious to you - We already have a web server bound to port 80. There can be only one.

This means turning off the web server, running lets encrypt, and then turning it back on. It's a viable option (and is the way I've done it in the passed), but let's do better.


I'm going to go with the "webroot" module, which is similar to how Google webmaster tools proves ownership. It creates a file in your web root that letsencrypt can check for, which then proves that you are actually installing an SSL on the server it can reach from your domain.

Note that this gets the certificate but does not install it. There are apache and nginx modules that can do this. The Apache module seems stable, but they plaster a scary "experimental" warning on the Nginx module, so I haven't used it.

Following the docs on the webroot option, I use the following command:

# Don't forget we're in /opt
# and all files are owned by root currently
cd /opt/certbot

# Run as root, which avoids python dependencies
# from trying to install in /home/ubuntu as user root
sudo su

# Note that I know and am using Nginx's default webroot location
# which I grabbed from /etc/nginx/sites-available/default
# Also note that --non-interactive --agree-tos --email <email> is required
# to avoid prompts (great for automating this!)
# Also remember we're running as root
./certbot-auto certonly --webroot -w /var/www/html \
    -d tutorial.serverops.io \
    --non-interactive --agree-tos --email chris@serversforhackers.com

I used tmux to split my terminal and ran sudo tail -f /var/log/nginx/access.log to see the access log as I ran the above command. I saw this: - - [01/Jun/2016:15:30:25 +0000] "GET /.well-known/acme-challenge/-IeTRWj2zRKRxim2ngMPUP3_Nvt6DN_TR-fLXUQMfHk HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

And the command itself outputs this:

 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/tutorial.serverops.io/fullchain.pem. Your
   cert will expire on 2016-08-30. To obtain a new or tweaked version
   of this certificate in the future, simply run certbot-auto again.
   To non-interactively renew *all* of your ceriticates, run
   "certbot-auto renew"
 - If you lose your account credentials, you can recover through
   e-mails sent to chris@serversforhackers.com.
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

We can check out that directory it mentions as well:

# Still running as user root
$ ls -lah /etc/letsencrypt/live/tutorial.serverops.io
total 8
drwxr-xr-x 2 root root 4096 Jun  1 15:30 ./
drwx------ 3 root root 4096 Jun  1 15:30 ../
lrwxrwxrwx 1 root root   45 Jun  1 15:30 cert.pem -> ../../archive/tutorial.serverops.io/cert1.pem
lrwxrwxrwx 1 root root   46 Jun  1 15:30 chain.pem -> ../../archive/tutorial.serverops.io/chain1.pem
lrwxrwxrwx 1 root root   50 Jun  1 15:30 fullchain.pem -> ../../archive/tutorial.serverops.io/fullchain1.pem
lrwxrwxrwx 1 root root   48 Jun  1 15:30 privkey.pem -> ../../archive/tutorial.serverops.io/privkey1.pem

Great, we can see those are symlinked to some other files. These get updated when we renew our certificates, but it's the symlink we use in our Nginx configuration, so we don't need to update web server configuration when we renew!

Nginx Configuration

Let's setup our Nginx configuration to use our new SSL certificate.

We'll edit /etc/nginx/sites-available/default and uncomment the SSL configuration portion.

server {
    listen 80 default_server;
    listen 443 ssl default_server;

    ssl_certificate      /etc/letsencrypt/live/tutorial.serverops.io/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/tutorial.serverops.io/privkey.pem;

    # Remaining removed for brevity

We use the fullchain.pem so we have the root certificate and the intermediary authorities. And of course we use the private key used to generate the certificate.

Once the changes are added, we can test Nginx and reload the configuration, assuming it doesn't find any issues.

sudo service nginx configtest
sudo service nginx reload

Now head to our domain with "https://" (https://tutorial.serverops.io) and we'll see the green lock of bliss!


Letsencrypt certificates are good for only 90 days, so you need to renew periodically. This is hella easy.

# again, as user root
cd /opt/certbot
./certbot-auto renew --webroot -w /var/www/html

This will renew any certificates expiring within 30 days. You can set this as a cron task and run it as much as you'd like (altho I'd run it on the first of each month personally).

Here's an example /etc/cron.monthly/letsencrypt bash file (make sure it's executable - sudo chmod u=rwx,go=rx /etc/cron.monthly/letsencrypt):

#!/usr/bin/env bash

cd /opt/certbot
./certbot-auto renew --webroot \
    --noninteractive \
    -w /var/www/html \
    --post-hook "service nginx reload"

Update: I've found that in some cases, the certificate does not renew (due to how and when Certbot decides a certificate is not yet ready for renewal). To counter-act this, I just add the --force-renewal flag:

#!/usr/bin/env bash

cd /opt/certbot
./certbot-auto renew --webroot \
    --force-renewal \
    --noninteractive \
    -w /var/www/html \
    --post-hook "service nginx reload"

Then (noted above, but repeated here as well) we need to make sure the script is executable:

sudo chmod +x /etc/cron.monthly/letsencrypt

We use the webroot module again and let is know the webroot path. Then we also set a post-hook to reload Nginx, to ensure it sucks in the new certificate.

If you want to test the renewal immediately, add the --force-renewal flag!

That's it! Your certificates will get renewed.