Lets Encrypt organized for reverse nginx proxy

This is a short post on using Let's Encrypt to get TLS certificates for use in Nginx virtual host sites. This post assumes:

  • Ubuntu
  • NGINX (running a number of domains)
  • Some form of scheduler (Cron, Rundeck, ..)
Let's Encrypt

Let's Encrypt is an initiative meant to allow anyone to receive a 3rd party Domain Validated (DV) certificate for server authentication (HTTPS, SMTPS, IMAPS, ..). They're here, also: Github.

Initial setup

In my home network, I have a number of VM's running various web services. As I have only one public facing IP, I use a web proxy to host my sites. This proxy runs an NGINX server with a number of virtual hosts each with their configuration. Most of the configs end with a proxy_pass statement redirecting traffic to the intended server.

In my setup, I wanted to be able to:

  • Quickly add new sites / domains
  • Let the proxy handle SSL (ssl acceleration)
  • Let the proxy worry about renewals
  • Generalize the configuration into "snippets" or similar reference-able things

I installed the letsencrypt package which gives you the letsencrypt binary at /usr/bin/letsencrypt. Using this, we can run the necessary commands to invoke the apis.

I created an NGINX configuration snippet at /etc/nginx/snippets/letsencrypt.conf with the following text:

location '/.well-known/acme-challenge' {
        default_type    "text/plain";
        root            /tmp/letsencrypt-auto;

This configuration instructs NGINX to forward the path /.well-known/acme-challenge to a special folder at /tmp/letsencrypt-auto (you can chose any folder). This snippet can be included in a lets encrypt site, like this:

include /etc/nginx/snippets/letsencrypt.conf;
Setting up a new site

These are the steps for a new site (here letsencrypt.mbwarez.dk). First, create the configuration at /etc/nginx/sites-enabled/letsencrypt.mbwarez.dk (I name all my configs after their FQDN).

server {
        listen 80;
        #listen 443 ssl;
        server_name letsencrypt.mbwarez.dk;

        #ssl_certificate         /etc/letsencrypt/live/letsencrypt.mbwarez.dk/fullchain.pem;
        #ssl_certificate_key     /etc/letsencrypt/live/letsencrypt.mbwarez.dk/privkey.pem;
        include                 /etc/nginx/snippets/letsencrypt.conf;
        include                 /etc/nginx/snippets/hpkp-sts.conf;

        root /var/www/letsencrypt.mbwarez.dk;

Observe how the certificate lines are commented out. This is due to the files being missing right now, and NGINX would refuse to start. We also only listen to HTTP (port 80) now. Also note that I have included the aforementioned snippet.

Then run the letsencrypt command:

/usr/bin/letsencrypt certonly --text --non-interactive --keep --email TODO_EMAIL --webroot --webroot-path=TODO_PATH -d TODO_DOMAIN

With relevant values replaced:

# /usr/bin/letsencrypt certonly --text --non-interactive --keep --email certificate-admin@mbwarez.dk --webroot --webroot-path=/tmp/letsencrypt-auto -d letsencrypt.mbwarez.dk

 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/letsencrypt2.mbwarez.dk/fullchain.pem. Your
   cert will expire on 2016-10-05. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If you like Let's Encrypt, please consider supporting our work by:

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

What happened was that the LE server attempted to fetch a file at http://letsencrypt.mbwarez.dk/.well-known/acme-challenge/some-file-name, which was redirected to the file at /tmp/letsencrypt-auto. As LE got what they expected, they issued a certificate in the requested name, and placed it at /etc/letsencrypt/live/letsencrypt.mbwarez.dk/.

Now we can simple uncomment the commented lines in the config:

server {
        listen 80;
        listen 443 ssl;
        server_name letsencrypt.mbwarez.dk;

        ssl_certificate         /etc/letsencrypt/live/letsencrypt.mbwarez.dk/fullchain.pem;
        ssl_certificate_key     /etc/letsencrypt/live/letsencrypt.mbwarez.dk/privkey.pem;
        include                 /etc/nginx/snippets/letsencrypt.conf;
        include                 /etc/nginx/snippets/hpkp-sts.conf;

        root /var/www/letsencrypt.mbwarez.dk;

.. reload NGINX and off we go.

Renewing a certificate

Once every 3 months, you must renew your certificates (before they expire). I've set a scheduled task to run every 7 days, which just runs the exact same command as before. In fact, I created a script for it:


mkdir -p $DIR

function updatecert() {
    echo "UPDATING cert for $DOMAIN"

    $TOOL certonly --text --non-interactive --keep --email certificate-admin@mbwarez.dk --webroot --webroot-path=$DIR -d $DOMAIN

updatecert blog.mbwarez.dk
updatecert cygwin.mbwarez.dk
updatecert letsencrypt.mbwarez.dk
updatecert mbwarez.dk
updatecert plex.mbwarez.dk
updatecert scm.mbwarez.dk

echo "RESTARTING nginx"

service nginx configtest && service nginx reload

exit $ErrCode

(also, when adding new sites, I simply add them to here instead of running the letsencrypt command myself).