cc by-sa flurdy

Let's Encrypt with Nginx

How to setup Let's Encrypt certificates for https with Ngnix, using AWS and proxying to Heroku

Started: February 2016. Last updated: 3rd September 2016.

Why Let's Encrypt

Traffic online can today easily be intercepted by criminals, stalkers, dodgy governments, nerdy neighbours or suspect employers. If this traffic is not encrypted then people can quickly make your life a misery (bullying, black mail, huge debts, false imprisonment, etc).

Previously setting up encrypted traffic with HTTPS web servers have been a tedious chore. Encryption has also been mixed with identification complicating the process. And was also very expensive. So I and many others have used self signed certificates to achieve encryption but they also cause browsers to prompt massive warnings of how dodgy your site is as there was no trusted identification of the certificate. So I rarely encrypted any sites, and I was not alone.

Let's Encrypt solves these problems. Formed in 2014 and launched in beta in 2015 by ISRG, Mozilla, Cisco, Akamai, EFF and others, Let's Encrypt offer properly signed certificates for free. Without any authentication. And with a lot of automation. And it is great.

This howto shows how to set up Nginx on an Ubuntu box, then install the Let's Encrypt's certification tools and set up a https website. Also includes an example on how to proxy SSL traffic to a Heroku application.

DNS

I am not here to tell how manage do your DNS as everyone has different requirements.

In general your first step is in advance to reduce the relevant records TTL time (to e.g. 1 minute) so that any changes are quickly propagated.

Also you should probably test this setup with a non important domain initially or even a subdomain. Once comfortable with each step and it is proven to work then apply it to more important websites/domain names.

Launch instance

Launch any server you prefer, locally, in Vagrant, Digital Ocean, Google Cloud Engine, etc. I will assume a nano instance on Amazon AWS with an Ubuntu 14.04 image. I also add a 2Gb EBS volume as swap to it as nano's only have 0.5Gb RAM. For me that is cheapest but if you have a popular site where the IO will be expensive obviously go for a higher memory instance instead with no swap.

Make sure the instance is up to date.

sudo apt-get update && sudo apt-get upgrade

Point the dns for your domain to the new server's ip if you use an Elastic IP. Temporarily with AWS you can CNAME your dns record to the public ip AWS name for your instance.

Setup Nginx

Install Nginx

sudo apt-get install nginx

Then lets create a simple web page (replace example.com with your domain and ubuntu with your username)

sudo mkdir -p /var/www/example.com/html;
sudo chown -R ubuntu:ubuntu /var/www/example.com/html;
vi /var/www/example.com/html/index.html
<html>
<head>
<title>Hello world</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>

Its configuration is under /etc/nginx. We will add one configuration by copying the default one.

cd /etc/nginx/sites-available;
sudo cp default example.com;
sudo vi example.com

You can leave the comments or stream line into something like:

server {
listen 80;
listen [::]:80;
server_name www.example.com;

root /var/www/example.com/html;
index index.html index.htm;

location / {
try_files $uri uri/ =404;
}
}

Activate the configuration and reload Nginx:

cd ../sites-enabled;
sudo ln -s ../sites-available/example.com;
sudo nginx -t && sudo service nginx reload;

Test via curl or browser on another and you should see the render of the simple page above.

curl www.example.com

Redirect other domains

Optionally add a few other domain names that redirects to the main server. Append it to the file we created above.

sudo vi /etc/nginx/sites-available/example.com server {
listen 80;
listen [::]:80;
server_name example.com example.org www.example.org;

return 302 http://www.example.com$request_uri;
}
sudo nginx -t && sudo service nginx reload;
curl example.org

Setup Let's Encrypt

The official tools for Let's Encrypt moves along very fast. At the time of writing it was v0.0.4β, undoubtedly it is already on a newer version. And for each iteration is more and more automatic , better default conventions, more and more useful features.

Linux distributions such as debian/Ubuntu can use apt-get to install the tools, but as it moves along so fast I prefer to just clone the git repo, which is also what they recommend at the time of writing.

cd /tmp;
sudo apt-get install git;
git clone https://github.com/letsencrypt/letsencrypt.git;
sudo mv letsencrypt /opt/;
sudo /opt/letsencrypt/letsencrypt-auto --help

Create certificates

Depending on which web server you use there are different options, but also the tools are getting automatic integration with the most popular ones. For now to do this to create your certificate with Nginx.

sudo /opt/letsencrypt/letsencrypt-auto certonly --webroot \
--agree-tos --email me@example.com \
-w /var/www/example.com/html -d www.example.com \
-d example.com -d example.org -d www.example.org

This creates certificates and configuration in /etc/letsencrypt.

ls -l /etc/letsencrypt/live/example.com

That command which takes seconds to run, has replaced weeks of work to create certificates, sending them to an authority to sign, paying them, retrieving the signed certificates, etc etc.

You can append more domains to the command with the -d argument. If you rerun the command on an existing certificate with more domains, the tool will allow that but will ask if you approve the expansion of domains covered by this certificate. Useful when you test initially with a lesser domain. Note as these certificates are free you can have as many separate certificates as you need.

Setup HTTPS

Now that we have certificates we append further to the Nginx configuration file we created earlier.

sudo vi /etc/nginx/sites-available/example.com server {
listen 443;
listen [::]:443;
server_name www.example.com;

root /var/www/example.com/html;
index index.html index.htm;

ssl on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

location / {
try_files $uri uri/ =404;
}
}
sudo nginx -t && sudo service nginx reload;
curl https://www.example.com

Redirect to HTTPS

We should now redirect the unencrypted site to encrypted site. Edit the initial first server block of the Nginx configuration.

sudo vi /etc/nginx/sites-available/example.com server {
listen 80;
listen [::]:80;
server_name www.example.com;

location '/.well-known/acme-challenge' {
root /var/www/example.com/html;
index index.html index.htm;
try_files $uri uri/ =404;
}

location / {
return 302 https://www.example.com$request_uri;
}
}
sudo nginx -t && sudo service nginx reload;
curl www.example.com

The '/.well-known/acme-challenge' section is to allow letsencrypt command's webroot option to check if you own the domain by putting a directory and files in your websites path. In the future you need to ensure that it still can do that.

You probably want to be a good citizen and redirect the encrypted alternative domains to https://www.example.com as well, so append this:

sudo vi /etc/nginx/sites-available/example.com server {
listen 443;
listen [::]:443;
server_name example.com example.org www.example.org;

ssl on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

location / {
return 302 https://www.example.com$request_uri;
}
}
sudo nginx -t && sudo service nginx reload;
curl https://example.org

Renew certificates

Let' Encrypt certificates by design are only valid for 90 days. This is to avoid the problem of stale old expired certificates, which often happen when they are valid for years, and people that know how to renew either forget how or leave the company.

But again Let's Encrypt can automate this with a one line command.

sudo /opt/letsencrypt/letsencrypt-auth renew

Append this to crontab to run every few weeks will ensure these certificates newer expire. There are more elaborate error logging versions available but this works for me.

sudo crontab -e 11 11 4/15 * * /opt/letsencrypt/letsencrypt-auto renew

Future versions of the tools will even create a crontab entry for you (probably already released by the time you read this).

Voila

That is it. You now have an encrypted site for free (apart from the server hosting costs).

Proxy Heroku

Applications on Heroku can use https, and it is free as it is under *.herokuapp.com certificate. However if you want to use your own custom domain, they would need a separate certificate and as such charge you $20 per month for essentially a load balancer in front of your application that encrypts the traffic. As a business that charge is negligible but as a hobby project it is not an option.

With this guide you can proxy this encrypted traffic and pay just ~$5 or less per month. And can share this across as many applications.

Open the previous main http server block in your Nginx configuration. And replace the static website parts with a proxypass.

sudo vi /etc/nginx/sites-available/example.com server {
listen 443;
listen [::]:443;
server_name www.example.com;

ssl on;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

location / {
proxy_pass https://example-live-01.herokuapp.com;
}
}
sudo nginx -t && sudo service nginx reload;
curl https://www.example.com

Feedback

Please fork and send a pull request for to correct any typos, or useful additions.

Buy a t-shirt if you found this guide useful. Hire Ivar for short term advice or long term consultancy.

Otherwise contact flurdy. Especially for things factually incorrect. Apologies for procrastinated replies.