Letsencrypt on AWS with Nginx

I'll try not get on a soapbox here, but personally I think it's a good idea to encrypt all of your websites, domains, intra-network cloud traffic, etc. I don't like the idea of people sniffing my traffic and there is something undeniably nice about having a "green padlock" on my website.

That said, one of the primary drawbacks of TLS encryption on small websites has been the cost. It is possible to self- sign certificates with a custom certificate authority, but if you attempt to use these certificates on your website, end users are warned (justifiably) that the CA may not be trustworthy. After obtaining a user's explicit consent, self- signed certificates can be just as secure, but they're typically only appropriate for internal communication lest you scare away an uninitiated audience.

So what can you do to get a TLS certificate signed by a trusted CA?

  1. You can pay through the nose to obtain industry standard certificates signed by racketeers – Comodo, Verisign, etc. This may be perfectly acceptable for your organization. You can get wildcard certificates (e.g. *.example.com`) for about 400 USD a year which may be a drop in the bucket compared to your other costs.
  2. You can get a certificate from sites like StartCom (formerly StartSSL?) who often restrict the usage to non-commercial applications.
  3. You can jump on the letsencrypt bandwagon. However you're limited to 5 certificates per week and wildcard certificates are not supported. This means you're probably out of luck unless you control the entirety of your domain and only need 5 subdomains covered. Note also that the letsencrypt certificates expire after 3 months instead of the typical 12 months.

If it wasn't apparent from the title here, we'll be going over option 3. I'll stress that letsencrypt isn't perfect, but I've found that it suits my needs. It is beta software and is subject to rapid iteration and susceptible to a few bugs and idiosyncrasies – especially if you're on an unsupported platform like AWS with AMI Linux. Fear not! It's actually relatively quick and easy to get set up.

Just so you know what's generally going on, the you'll be downloading the letsencrypt repository and running an executable that creates files on your system in a target directory. The letsencrypt servers will attempt to read these files and then, assuming they match what is expected, will create a full chain .pem file and private key on the same machine.

Preliminary Steps

Before getting started you'll want to:

  1. Create a new EC2 instance in a public subnet running AMI linux (just set it up like you'd set up a webserver).
  2. Install git on the instance – sudo yum install git
  3. Install nginx on the instance
  4. Make sure port 80 is open and accessible
  5. Make sure the subdomain you'd like to obtain a certificate for resolves to the box that will be running the letsencrypt executable

If you need to run letsencrypt on a separate machine (e.g. you have security concerns or you're managing a group of servers behind a load balancer) then you'll need to set up a network file share so that each webserver can respond identically to challenge requests from the letsencrypt authority.

Configuring Nginx

You'll need this block somewhere in the http block of your configuration:

server {
    listen 80 default_server;

    location /.well-known/acme-challenge {
        default_type "text/plain";

        root /var/lib/nginx/letsencrypt;
    }
}

Here the /var/lib/nginx/letsencrypt folder is simply a webroot folder and can be any folder you'd like. If setting up a network share as previously mentioned, make sure that the remote drive is mounted to the webroot folder.

If you already have a default server set up on port 80, then simply merge the location block into that section. If not, your overall nginx configuration should look something like:

worker_processes 1;
pid /var/run/nginx.pid;
user nginx nginx;

error_log /var/log/nginx/error.log;

events {
    worker_connections 1024;
}

http {
    default_type application/octet-stream;

    access_log /var/log/nginx/access.log;

    server {
        listen 80 default_server;

        access_log /dev/null;

        location /.well-known/acme-challenge {
            default_type "text/plain";

            root /var/lib/nginx/letsencrypt;
        }
    }
}

You'll want to make sure that the user running the nginx process (in this case the nginx service user I created) has read/write access to /var/lib/nginx/letsencrypt and /var/log/nginx (or whatever folders you choose for webroot and logs).

After all of this is setup, start the nginx daemon with sudo service nginx start. When you install nginx with make it does not include an init script, so you'll have to create one yourself or just execute the binary manually using the configuration you wrote.

Setting up Letsencrypt

First you'll want to checkout the letsencrypt repository. As per the official warnings don't run the letsencrypt-specific commands as root or with the sudo directive.

git clone https://github.com/letsencrypt/letsencrypt

Change directory into the folder that command creates (letsencrypt) and run:

cd letsencrypt
git tag

You should see a list of tags that are stored in the repository. I've tested this with v0.4.0, but you're likely to see later versions that you can use if you'd like.

git checkout v0.4.0

This will put you in a detached branch. Execute:

./letsencrypt-auto --help --debug

This command should set up most of the files appropriately but should then fail.

You'll then need to install the Python 2.7 development headers since the automated letsencrypt script does something wonky and doesn't solve the problem for you.

sudo yum install python27-devel

You'll also need to install the virtualenv Python package into the 2.7 Python library. Assuming your python command maps to your system Python 2.7 and your easy_install binary maps to the 2.7 Python library:

easy_install pip
pip install virtualenv

Then re-run the command:

./letsencrypt-auto --help --debug

And it should work.

Obtaining Certificates

Finally we'll be executing the same letsencrypt script with different parameters to obtain a certificate:

./letsencrypt-auto certonly --webroot -w /var/lib/nginx/letsencrypt/ -d www.example.com -d example.com

You'll be prompted for an admin email and asked to accept the terms of use. It's probably easiest to just use the same email address you use for your DNS administration, just something you know you'll have access to.

While you cannot obtain wildcard certificates, you can specify up to ~100 -d parameters for a single certificate. So you should be easily covered if you're not provisioning subdomains for people.

Potential points of failure include:

  1. The ACME challenge files cannot be created due to local file/folder permission issues.
  2. The ACME challenge files cannot be read by the letsencrypt authority over HTTP (closed ports, junky nginx config, etc.).
  3. You've exceeded your limit of 5 certificates per week.

If you run into issues, you can check the webroot documentation for more information.