Better SSL/TLS Certificates from Let’s Encrypt with NGINX and Cloudflare

Benjamin Caldwell
3 min readJul 9, 2016

--

Let’s Encrypt is a free open certificate authority that allows you to secure your webserver using SSL/TLS certificate for free. These certificates are trusted by the majority of web browsers.

Traditionally, setting up Let’s Encrypt with NGINX required either temporarily disabiling NGINX to get the certificate or adding an acme challenge or adding an acme challenge route to your nginx config. Both of these methods are tedious and not easily automatable. Additionally these method use the let’s encrypt client which is large to download and overkill in most cases.

To improve this process I used letsencrypt.sh by lukas2511 and the cloudflare api so that I don’t have to mess with NGINX to get a certificate. I chose to do this by using an ansible role. (https://galaxy.ansible.com/benjamincaldwell/letsencrypt/). To use this role, use the playbook in the readme as a guide. This role automates the procedure outlined below.

Install dependencies

To use this method a couple of packages are required.

apt-get update
apt-get install git python-pip build-essential libssl-dev libffi-dev python-dev

Clone required github repositories

git clone https://github.com/lukas2511/letsencrypt.sh.git
git clone https://github.com/kappataumu/letsencrypt-cloudflare-hook.git letsencrypt.sh/hooks/cloudflare

Install python dependencies

Next we need to install the python requirements.

To do this, first create a python virtual environement and then install the requirements.

pip install virtualenv
cd letsencrypt.sh
virtualenv venv
source venv/bin/activate
pip install -r hooks/cloudflare/requirements-python-2.txt

Setting up the environment

Go to the Cloudflare settings and copy your Global API Key and export it into your environment along with your cloudflare email.

export CF_EMAIL=CF_email
export CF_KEY=CF_key

Get the certificate

Before getting the certificate we must first create a domains.txt file. An example domains.txt file is below:

letsencrypt.sh/domains.txt

example.com www.example.com
example.net www.example.net wiki.example.net

This states that two certificates should be required: one for example.com and the other for example.net, with the other domains in the line corresponding to alternative names.

Next we must configure letsencrypt.sh. Use the following as a template and fill in the values for your contact email.

letsencrypt.sh/config

#!/usr/bin/env bash
CHALLENGETYPE="dns-01"
HOOK="hooks/cloudflare/hook.py"
# E-mail to use during the registration (default: <unset>)
CONTACT_EMAIL="EMAIL"

To get the certificate run.

./letsencrypt.sh -c

Set up a cron job

Since the certificate from Let’s Encrypt only lasts for 3 months it is a good idea to set up a cron job to automate this process.

To do this create a cron.sh file in your letsencrypt.sh folder as shown below. Don’t forget to fill in you cloudflare information.

#!/usr/bin/env bash
export CF_EMAIL=CF_email
export CF_KEY=CF_key
datesource /home/letsencrypt/letsencrypt.sh/venv/bin/activate
/home/letsencrypt/letsencrypt.sh/letsencrypt.sh -c
echo “”service nginx restart

To set up a monthly cron job open the cron job file

crontab -e

and add the following. Don’t forget to set the path to the path where the letsencrypt.sh folder is.

#Renew ssl cert
@monthly PATH/letsencrypt.sh/cron.sh >> PATH/letsencrypt.sh/cron.log 2>&1

Setting up nginx to use the certificate

The next step is to configure NGINX to use the certificate generated by Let’s Encrypt. An example config is shown below. Finally, don’t forget to fill in the proper path to the certificate and restart NGINX.

server {
listen 80 default_server;
listen [::]:80 default_server;

# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

# certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
ssl_certificate PATH/letsencrypt.sh/certs/DOMAINNAME/fullchain.pem;
ssl_certificate_key PATH/letsencrypt.sh/certs/DOMAINNAME/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

## [Optional] Generate a stronger DHE parameter:
## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
ssl_dhparam /etc/ssl/certs/dhparam.pem;

# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
ssl_prefer_server_ciphers on;

# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;

# OCSP Stapling ---
# fetch OCSP records from URL in ssl_certificate and cache them
ssl_stapling on;
ssl_stapling_verify on;

## verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate PATH/letsencrypt.sh/certs/DOMAINNAME/chain.pem;

....
}

--

--