Deploying Let's Encrypt Free SSL Certificates

PUBLISHED ON NOV 16, 2016

Using Let’s Encrypt to generate Signed SSL certificates

Let’s Encrypt is a new SSL registrar sponsored by large industry names including Cisco, Mozilla, Facebook and many others. It provides free and automated SSL certificates using the ACME protocol for registering and renewing certificates. This is useful in places where a self-signed certificate may have been used in the past. Let’s Encrypt is currently in Public Beta, it is not currently recommended to use these certificates for customer facing environments or where EV certificates are needed.

Let’s Encrypt provides an official client which can automate the configuration and renewal of the SSL certificates. This client runs as a daemon on the server and also edits your Apache or nginx configurations. In many cases the behaviour of the official client is not ideal, luckily third party packages exist which simplify the deployment and renewal of certificates.

This guide will use the acme-tiny script to create, register and renew the certificates. This is a small (200 lines) python script which supports the ACME protocol. The small size of the script means it is easily auditable and deployable to various clients. The acme-tiny package is available at https://github.com/diafygi/acme-tiny Creating Let’s Encrypt certificates

Creating Account Key

Let’s Encrypt clients must have a public key registered to sign certificate requests with.

Generate the account key with OpenSSL:

openssl genrsa 4096 > account.key

Create Private Key and CSR for Domain

The ACME protocol uses CSR’s for issuing and renewal of certificates.

Generate domain private key

openssl genrsa 4096 > domain.key

Create CSR

Single domain:

openssl req -new -sha256 -key domain.key -subj "/CN=yoursite.com" > domain.csr

Multiple domains (ex. roote.ca + www.roote.ca):

openssl req -new -sha256 -key domain.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:yoursite.com,DNS:www.yoursite.com")) > domain.csr

Create directory for webserver to confirm ownership of domain

In order for Let’s Encrypt to ensure that the domain is under the requestors control a predefined URL path is used. In this location the acme-tiny script will place public files used during the registration and renewal process.

Create challenge directory to host files (path can be configured as needed)

mkdir -p /var/www/challenges/

Configure webserver to serve files from this directory, a 404 is served if the exact URL doesn’t match the public signing filename:

Example for nginx:

server {
    listen 80;
    server_name yoursite.com www.yoursite.com;
    location /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;
    }

    ...the rest of your config
}

Example configuration for Apache:

Alias /.well-known/acme-challenge/ /var/www/challenges/
<Directory "/var/www/challenges/">
    Options None
    AllowOverride None
    ForceType text/plain
    RedirectMatch 404 "^(?!/\.well-known/acme-challenge/[\w-]{43}$)"
</Directory>

Create the Signed Certificate

This command will use the acme-tiny script with your account key and previously generated CSR to register the certificate. A signed certificate is returned at the end of the process.

python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /var/www/challenges/ > ./signed.crt

Install the certificate on the webserver

At this point you will have the domain private key (domain.key) and signed certificate (signed.crt). Move these files to the needed directory and configure the webserver to use the new certificate files

If using nginx the certificate must be in .pem format. To convert the certificate download the Let’s Encrypt intermediate certificate and concatenate it with your generated certificate:

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

Example SSL configuration for nginx:

server {
    listen 443;
    server_name yoursite.com, www.yoursite.com;
    ssl on;
    ssl_certificate /path/to/chained.pem;
    ssl_certificate_key /path/to/domain.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256- SHA:DHE-RSA-AES128-SHA;
    ssl_session_cache shared:SSL:50m;
    ssl_dhparam /path/to/server.dhparam;
    ssl_prefer_server_ciphers on;
    ...the rest of your config
}
server {
    listen 80;
    server_name yoursite.com, www.yoursite.com;
    location /.well-known/acme-challenge/ {
        alias /var/www/challenges/;
        try_files $uri =404;
    }
    ...the rest of your config
}

Creating an auto renewal cronjob

Let’s Encrypt certificates are valid for 90 days. In order to ensure the certificate does not expire the renewal process can be added as a cronjob in the system.

renew_cert.sh:

#!/bin/bash

echo "`date` Renewing certificate for: $1"
python /var/acme/acme_tiny.py \
    --account-key /var/acme/cert/user.key \
    --csr /var/acme/cert/$1.csr \
    --acme-dir /var/www/challenges/ > /var/acme/cert/$1.crt

cat /var/acme/cert/$1.crt /var/acme/lets-encrypt-x3-cross-signed.pem > /etc/nginx/ssl/$1.pem

echo "Certificate for: $1 renewed"

systemctl reload nginx

Add script to crontab to run once a month:

0 0 1 * * root /var/acme/renew_cert.sh roote.ca &>> /var/log/acme.log