Chris Dzombak

Deploying Let’s Encrypt with Nginx on Ubuntu 16.04

This post will (tersely) walk through the steps required to deploy Let’s Encrypt for TLS, including automatic renewals, on an Ubuntu 16.04 LTS1 server running Nginx. This will allow Let’s Encrypt to fetch and renew your certificates transparently, without interfering with whatever application you may be reverse-proxying with Nginx. We’ll use Let’s Encrypt’s webroot verification method. The Nginx configurations included will work for a server listening on IPv4 and IPv6 and will support HTTP2.

Assumptions: I assume:

System Setup

Install Certbot:

sudo add-apt-repository ppa:certbot/certbot
sudo apt update
sudo apt install python-certbot-nginx

Add the following line to root’s crontab via sudo crontab -e. This will make Certbot check daily whether any certificates on this system are due to be renewed.

20 3 * * * certbot renew --quiet

Put the following script at /etc/letsencrypt/renew-hooks/deploy/nginx (you may need to sudo mkdir -p /etc/letsencrypt/renew-hooks/deploy first). This script will reload Nginx after a certificate renewal completes.

#!/usr/bin/env bash

systemctl reload nginx

Nginx Integration

Place the following content in a new file, at /etc/nginx/snippets/letsencrypt.conf. This will be included in the non-HTTPS configuration for your site, allowing Let’s Encrypt to verify domain ownership via the webroot verification method:

location ^~ /.well-known/acme-challenge/ {
    default_type "text/plain";
    root /var/www/letsencrypt;
}

And create the directory referenced by that configuration file. This is where Certbot will place temporary domain verification files:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge

Site Setup

Now that Let’s Encrypt/Certbot is set up on this system, we can start configuring your site/service. Integrate the following configuration in your HTTP server block:

server {
    # Listen on port 80 for [www.]domain.com, on IPv4 and IPv6:
    server_name DOMAIN.com www.DOMAIN.com;
    listen [::]:80;
    listen 80;

    # Route .well-known to the verification directory we’ll tell Let’s Encrypt to use:
    include /etc/nginx/snippets/letsencrypt.conf;

    # Redirect all requests to the HTTPS version of the site:
    location / {
        return 301 https://www.DOMAIN.com$request_uri;
    }
}

sudo service nginx reload to make those changes effective.

Don’t create an HTTPS server block just yet. Instead, we’ll tell Certbot to request a certificate from Let’s Encrypt:

sudo certbot certonly --webroot --agree-tos --no-eff-email --email ME@DOMAIN.COM -w /var/www/letsencrypt -d www.DOMAIN.com -d DOMAIN.com

Note the first domain passed is included in the filename for the resulting certificate, key, etc. on disk.

You should see a message like this:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator webroot, Installer None
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for www.DOMAIN.com
http-01 challenge for DOMAIN.com
Using the webroot path /var/www/letsencrypt for all unmatched domains.
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/www.DOMAIN.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/www.DOMAIN.com/privkey.pem
   Your cert will expire on 2018-04-26. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - 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

Now we can proceed to use that new certificate, configuring HTTPS server blocks for our site in Nginx:

server {
    # Listen for www.DOMAIN.com on port 443 on IPv4 and IPv6:
    server_name www.DOMAIN.com;
    listen [::]:443 ssl http2;
    listen 443 ssl http2;

    # SSL configuration:
    ssl_certificate /etc/letsencrypt/live/www.DOMAIN.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.DOMAIN.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/www.DOMAIN.com/fullchain.pem;
    include /etc/nginx/snippets/ssl.conf;

    # Whatever configuration serves your site/application goes here.
}

server {
    # Redirect non-www accesses to www.DOMAIN.com:
    server_name DOMAIN.com;
    listen [::]:443 ssl http2;
    listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/www.DOMAIN.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.DOMAIN.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/www.DOMAIN.com/fullchain.pem;
    include /etc/nginx/snippets/ssl.conf;

    location / {
        return 301 https://www.DOMAIN.com$request_uri;
    }
}

The ssl.conf referenced above is posted in this Gist. It’s based on the “modern” configuration from the Mozilla SSL config generator; you may want to configure your service differently depending on your requirements.

sudo service nginx reload, and you should now be able to access your service via HTTPS at https://www.DOMAIN.com.

Housekeeping

You can test the renewal process with Certbot by running sudo certbot renew --dry-run. This won’t fetch a new cert, but it will make sure all the requirements are in place for Certbot to automatically renew your certificates.

Sign up to monitor your domain with SSLMate CertSpotter and Facebook’s CT Monitoring tool. This will alert you when a new certificate is issued, and the resulting emails can serve as a reminder to go verify that Nginx is serving the new cert when Certbot renews it.

And finally, you can test your TLS configuration with SSLLabs.


1: These instructions may well apply to other versions or other similar operating systems.