Secure Your Phoenix App With Free SSL

This is a follow up on the Phoenix deployment guide I recently published:

Deploy Early and Often: Deploying Phoenix with Edeliver and Distillery

Introduction

After deploying your application, you’ll want to set up automated backups and SSL. TLS/SSL is the standard technology that enables encryption of data being sent between two systems. In this case, it’s between your Phoenix application and your user’s browser. There’s no need to go into the benefits of HTTPS here—if you’re reading this, I’m sure you are already convinced.

Let’s Encrypt is a Certificate Authority that “exists to help create a 100% encrypted web”, and they’re doing it by giving you free SSL. If you’ve installed SSL certificates from vendors like Comodo or Thawte, the main difference is that Let’s Encrypt certificates are only valid for 90 days while those you purchase have validity in yearly increments. The other difference is that Let’s Encrypt offers only Domain Validation (DV) certificates and not Organization Validation (OV), Extended Validation (EV), or wildcard certificates.

In this guide we will be using certbot to obtain a free Let’s Encrypt SSL certificate and then automate the renewal process.

We won’t be going into the details of the numerous settings in our configuration. Nonetheless, it’s important to know what we’re securing against. Here’s a great article that explains what the various components mean.

Our Setup

If you’ve followed the guide, this is what we have so far:

  • Ubuntu 16.04 on DigitalOcean’s 1GB RAM plan
  • A simple Phoenix application, deployed with Edeliver and Distillery
  • DNS A Record pointing your domain to the public IP address of your server. For this guide, we’re pointing example.com and www.example.com to 188.166.182.170.
  • Nginx is serving the Phoenix app on port 80 (HTTP).

Installing Certbot

First, let’s add the newest certbot we can find. Add the repository:

sudo add-apt-repository ppa:certbot/certbot
# press [ENTER] to continue ...

Now we can update the package list and install certbot.

sudo apt-get update 
sudo apt-get install certbot

Getting Our First SSL Certificate

We’ll use the Webroot plugin to get our certificates. It works by creating a temporary file for each of your requested domains in ${webroot-path}/.well-known/acme-challenge. The Let’s Encrypt server then makes HTTP requests to validate that the files exist on the server that your domain resolves to. The request would look like:

GET http://example.com/.well-known/acme-challenge/HF7HU1IeTW4kY_Z6UIyaakzOkyQgPr_yBW5gYXaQ2UX

Let’s add a location block to our Nginx config file that points /.well-known to /home/deploy/certbot/.well-known. Before we do that, we’ll need to create the certbot folder in our home directory.

cd ~
mkdir certbot
sudo vim /etc/nginx/sites-enabled/deploy_phoenix

Check your Nginx config for syntax errors, and if no errors are found, restart Nginx.

sudo nginx -t
sudo systemctl restart nginx

We’ll now use Webroot to request an SSL certificate, passing in /home/deploy/certbot/ as our webroot-path. In order for a single cert to work with multiple domain names, be sure to include all of them (in our example we’re using example.com and www.example.com).

sudo certbot certonly --webroot --webroot-path=/home/deploy/certbot/ -d example.com -d www.example.com

You’ll be prompted to enter your email address and agree to the terms of service. After that, you’ll see a message that the certificates have been generated and stored. Note the path and expiration date of the SSL certificate. The path should be /etc/letsencrypt/live/example.com/.

Aside: You can backup your letsencrypt credentials and certs with:

sudo tar zcvf /tmp/letsencrypt_backup_$(date +’%Y-%m-%d_%H%M’).tar.gz /etc/letsencrypt

Generate a Diffie-Hellman Key

Weak Diffie-Hellman(DH) key exchange parameters could make your SSL connection vulnerable to man-in-the-middle attacks. Let’s generate strong DH parameters to increase security. This should take about 5 minutes.

sudo openssl dhparam -out /etc/letsencrypt/dhparam.pem 2048

Configuring SSL on Nginx

Now that we have our certificates and strong DH parameters, we’re ready to configure Nginx to use them.

Snippets for certificates and settings

First, let’s create a snippet for our certificate files.

sudo vim /etc/nginx/snippets/ssl-example.com.conf
Paste in the above. Remember to replace example.com with your domain name.

Save and close the file. Next, we’ll create a snippet for SSL settings using the recommendations on Cipherli.st.

sudo vim /etc/nginx/snippets/ssl-params.conf
Uncommenting line 18 provides increased security but you must understand the implications.

Save and close this file too.

Updating our Nginx config file

Now we’ll edit our config file.

# Let's back up our config file first.
sudo cp /etc/nginx/sites-available/deploy_phoenix /etc/nginx/sites-available/deploy_phoenix.backup
sudo vim /etc/nginx/sites-available/deploy_phoenix

It’s a long file, but the main additions are the redirection blocks and the inclusion of our SSL snippets. Remember to replace example.com with your own domain name.

Save the file and close it. Let’s check that we have no syntax errors in our config and snippet files.

sudo nginx -t

If there are no errors, proceed with restarting Nginx to make our changes go live.

sudo systemctl restart nginx

We need to edit our firewall to allow HTTPS traffic on port 443.

sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP' # remove the redundant profile
sudo ufw status

You should see the following:

Now for the moment of truth. Did our configuration work? Let’s visit our domain in a web browser. Visiting http://example.com should redirect to https://example.com.

Put your website through the Qualys SSL Server Test. It should report an A+ rating.

You should be getting an A+ rating too if you’ve followed along. Note that I have HSTS enabled (let me know in the comments what the rating is without HSTS if you’ve left it commented out). See the comment in the ssl-params.conf snippet earlier for more details.

SSL Cert Auto Renewal

Let’s Encrypt’s certificates expire in 90 days. We’ll need to set up a cron job to renew it for us automatically.

sudo crontab -e

Paste in the following at the end of the file. You may choose a different time. --renew-hook will reload Nginx, but only if a renewal has occurred.

It’s a good idea to put the expiry date of the certificate into your calendar so you can check back a few days before to see if your renewals are being performed.

Updating Phoenix Production config

One last thing to do. We need to update config/prod.exs to use the HTTPS scheme.

Increment the version number in mix.exs, commit your changes and deploy your release.

git commit -a -m "edited prod.exs to add HTTPS scheme"
git push origin master
mix edeliver build release production --verbose
mix edeliver deploy release to production
mix edeliver stop production
mix edeliver start production

Conclusion

In this guide, we’ve used Let’s Encrypt’s Certbot to help us get SSL certificates, and we’ve set up automatic renewal. Our Phoenix application now keeps our visitors’ connections private and secure.

Happy coding!