Background

Recently I’ve been messing around with Let’s Encrypt and have found a simple way to auto-renew SSL certs without requiring the nginx beta plugin. Let’s Encrypt is a new Certificate Authority which provides free, simple to obtain SSL certificates for your website(s). The entire process and client is open source and is audited by professionals all around the world. As of writing this post, the client is still in public beta, hence the lack of support for true Nginx support.

This guide aims to teach you how to automate the SSL renewal process even though Let’s Encrypt doesn’t fully support Nginx in this way, yet. It will take about 1/2hr if you don’t already have Nginx setup and your initial certificates generated, otherwise it should only take a couple of minutes!

Prerequisites

This guide is based off of my Raspberry Pi 2, which is running raspbain jessie lite. Before following this guide you will need to have Nginx setup, along with you initial certificates generated (follow steps 1-3, this guide serves as a modification of step 4).

Automating SSL Renewal

At the time of writing this post, SSL certificates generated by Let’s encrypt are only valid for a short period of 90 days. They do this since the renewal process should be automatic, and if one of your certificates is compromised then it will only be valid for a short period of time instead of multiple years. To automate the process I chose to use the webroot authentication method with a simple cronjob to renew my scripts.

To take advantage of automatic renewals with Nginx, we will be using the webroot feature of Let’s Encrypt which allows us to specify a directory which the Let’s Encrypt client can tell their server to look for a specific hidden file to verify your domain. You can read their official documentation on webroot authentication & renewals here. The reason we choose to use the webroot plugin instead of the standalone option is so that we don’t need to stop the nginx service to renew the certificates.

Preparing to use the webroot plugin

Before we can renew our SSL certificates we must prepare Nginx to serve up a special location. To do this we will make a special Nginx configuration which will ensure Let’s Encrypt has access to the /.well-known/acme-challenge/ directory on your server, for each of your domains. A special configuration has been created for this so all you need to do is add include letsencrypt-acme-challenge.conf; to your ssl server block of each domain/subdomain you wish to have SSL auto renew on.

Special thanks to renchap for this configuration:

letsencrypt-acme-challenge.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx)
# We use ^~ here, so that we don't check other regexes (for speed-up). We actually MUST cancel
# other regex checks, because in our other config files have regex rule that denies access to files with dotted names.
location ^~ /.well-known/acme-challenge/ {

# Set correct content type. According to this:
# https://community.letsencrypt.org/t/using-the-webroot-domain-verification-method/1445/29
# Current specification requires "text/plain" or no content header at all.
# It seems that "text/plain" is a safe option.
default_type "text/plain";

# Do NOT use alias, use root! Target directory is located here:
# /etc/nginx/renew-ssl/www/.well-known/acme-challenge/
root /etc/nginx/renew-ssl/www;
}

# Hide /acme-challenge subdirectory and return 404 on all requests.
# It is somewhat more secure than letting Nginx return 403. Ending slash is important!
location = /.well-known/acme-challenge/ {
return 404;
}

Now that you have included that configuration in each of your server blocks, we must create the directory which will serve as the webroot for our well-known directory. In my case I created the directory /etc/nginx/renew-ssl/www/. Note that line 17 of the configuration also points to this directory, you must modify that if you change the location!

To ensure that all of your configurations are valid run sudo nginx -t if everything checks out you can reload nginx with sudo service nginx reload.

Creating Configuration Files

The configuration files will tell Let’s Encrypt which domains it should attempt to renew. First we must create a directory to store the configurations, to keep everything together I put them in /etc/nginx/renew-ssl/ssl-available. Create an individual configuration for each of your SSL certificates each ending in .ini and place them in the ssl-available directory created earlier.

example.com.ini
1
2
3
4
5
6
7
rsa-key-size = 4096

email = you@example.com

domains = example.com, www.example.com

webroot-path = /etc/nginx/renew-ssl/www

By default the renewal script, as seen below, will look in /etc/nginx/renew-ssl/ssl-enabled for the configuration files to provide to Let’s Encrypt. I chose to use the same concept as sites-enabled and sites-avalaible which Nginx uses so that you can easily enable and disable configurations by linking and unlinking them. To link your SSL configurations run sudo link -s ../ssl-avaliable/example.com.ini where example.com.ini is your Let’s Encrypt configuration ini. To disable a configuration just run sudo unlink example.com.ini where example.com.ini is the Let’s Encrypt config you wish to disable.

Setting Up a Renewal Script

I have created a modified version of the renewal script provided at Digital Ocean’s tutorials. This simple shell script will verify that the SSL certificates will be expiring in less than 60 days, and if they are then it will automatically run the renew command by passing in the configurations you created earlier. We will schedule this script to run once a week via a cron job so that you can guarantee that your certificates will renew before they expire. Save the following script to /usr/local/sbin/ as renew-ssl:

renew-ssl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/bin/bash

# Configuration
web_service='nginx' # name of service to reload
reload_web=true # Should the web_service be reloaded?_
le_path='/opt/letsencrypt' # Path to let's encrypt
config_path='/etc/nginx/renew-ssl/ssl-enabled/' #Path to LE configuration ini files
exp_limit=30; #Threshold of days before your cert expires, will renew if under this threshold

#vars
count=0;

for config_file in $config_path*.ini; do
if [ -f "$config_file" ]; then
echo "Analyzing file: $config_file"
domain=`grep "^\s*domains" $config_file | sed "s/^\s*domains\s*=\s*//" | sed 's/(\s*)\|,.*$//'`
cert_file="/etc/letsencrypt/live/$domain/fullchain.pem"

if [ ! -f $cert_file ]; then
echo "[ERROR] certificate file not found for domain $domain."
fi

exp=$(date -d "`openssl x509 -in $cert_file -text -noout|grep "Not After"|cut -c 25-`" +%s)
datenow=$(date -d "now" +%s)
days_exp=$(echo \( $exp - $datenow \) / 86400 |bc)

echo "Checking expiration date for $domain..."

if [ "$days_exp" -gt "$exp_limit" ] ; then
echo "The certificate is up to date, no need for renewal ($days_exp days left)."
else
echo "The certificate for $domain is about to expire soon. Starting webroot renewal script..."
$le_path/letsencrypt-auto certonly -a webroot --agree-tos --renew-by-default --config $config_file
echo "Renewal process finished for domain $domain"
count++;
fi
fi
done

if [ $count -gt 0 -a $reload_web = true ]; then
echo "Reloading $web_service"
/usr/sbin/service $web_service reload
echo "Renewal process finished, $web_service reloaded"
else
echo "Renewal process finished, $web_service not reloaded"
fi

Next make the script executable by running sudo chmod +x /usr/local/sbin/renew-ssl. Make sure the configuration on lines 4-8 matches your setup. You can now manually renew your certs by running sudo renew-ssl.

output
1
2
3
4
5
6
Analyzing file: /etc/nginx/renew-ssl/ssl-enabled/blog.fletchto99.com
Checking expiration date for blog.fletchto99.com...
The certificate is up to date, no need for renewal (85 days left).
Analyzing file: /etc/nginx/renew-ssl/ssl-enabled/pi.fletchto99.com
Checking expiration date for pi.fletchto99.com...
The certificate is up to date, no need for renewal (86 days left).

Automating the Renewal Script

The last step of the renewal process is automating the script, which is where a cron job comes into play. To add the cron job run sudo crontab -e to edit your crontab. Include the following snippet at the bottom of your crontab:

crontab-line
1
30 2 * * 1 /usr/local/sbin/ssl-renew >> /var/log/ssl-renewal.log

Save and exit the crontab. This will create a new cron job which will run the renew ssl command every Monday at 2:30 am. The output of the command will be logged to a file located at /var/log/ssl-renew. Now you can sit back and watch your SSL certificates renew themselves with out any work required!

Adding More Domains/Subdomains

In the future when you want to add a new domain/subdomain to the automatic renewal process all you need to do is create a new configuration file for it!

Conclusion & Thanks

I hope this short guide has inspired you to use SSL with your Nginx configuration. This was my first time messing arounnd with SSL/Let’s Encrypt and it has been a great learning experience!

This guide wouldn’t have been possible without the help of these resources: