Phoenix/Elixir App Secured with Let’s Encrypt

Andrew Forward
4 min readJan 7, 2016

--

Originally posted on January 7, 2016. The article was updated May 31, 2017 based on the many wonderful updates to Let’s Encrypt and Elixir/Phoenix.

In this article we are going to setup a Phoenix web application (running Elixir) with SSL configured using LetsEncrypt (i.e. all traffic running through SSL / HTTPS) and certbot.

Let’s Encrypt continues to promising, and a year since I first wrote this article looks like a lot has changed. First, there’s much better support for Nginx (so my other article is now very out of date).

Before we get started, you will need a few things installed first, I have provided links to installation scripts for Ubuntu 16.04 if you don’t already have an environment up and running.

Older versions might still work, so don’t go upgrading just yet; try things out and let me know if you run into problems.

Let’s create a new app and configure it for port 80

echo "Y" | mix phx.new isafe
cd isafe
vi config/dev.exs # and/or config/prod.exs

Change the port from 4000 to 80. Please note this isn’t an article about a production deploy of your application (refer here for that). Let’s start up your server

MIX_ENV=PROD mix phx.server

You should now see your site live (on port 80)

Next, we will need to tweak our routes to enable webroot authorization of Lets Encrypt. Let’s edit our endpoint.ex

vi ./lib/isafe/web/endpoint.ex

We need to allow anything from .well-known to route through Plug.Static for Let’s Encrypt authentication.

plug Plug.Static,
at: "/", from: :isafe, gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt .well-known)

Restart your server!

MIX_ENV=prod mix phx.server

Note that I updated the instructions to work with prod, as there were some issues with renewals as noted on StackOverflow). The major change is that assets (aka images, JSON, .well-known files) will be drawn from the _build directory. Let’s test our change:

mkdir -p ./_build/prod/lib/isafe/priv/static/.well-known
(cd ./_build/prod/lib/isafe/priv/static && \
echo "hello world" > .well-known/XXXYYY.html)

Open your browser to that page, and you should see the text below.

If you see an error like the following then something went awry (maybe you didn’t restart your server, or didn’t edit the correct file).

Now that we have our application ready, let’s install Let’s Encrypt certbot.

If you are running Ubuntu 16.04, the script probably still looks like the following:

(apt-get install -y software-properties-common && \
add-apt-repository -y ppa:certbot/certbot && \
apt-get update && \
apt-get install -y certbot)

Let’s write our configs to a file (so that we don’t need to remember all the options).

vi /etc/letsencrypt/letsencrypt.ini

Here is a template for what to put in there. Here is the documentation about all available settings.

rsa-key-size = 4096
email = example@email.com
domains = yourdomain.com, www.yourdomain.com
text = True
authenticator = webroot
preferred-challenges = http-01
webroot-path = /src/isafe/_build/prod/lib/isafe/priv/static

Don’t forget to update your domain, email and source path. Once configured we can generate our certificates

echo "A"| certbot certonly --config /etc/letsencrypt/letsencrypt.ini

If everything worked you should see something like

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/yourdomain.com/fullchain.pem. Your cert
will expire on 2017-08-30. To obtain a new or tweaked version of
this certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run "certbot
renew"
- If you like Certbot, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

Let’s add the https information to your config (e.g config/prod.exs)

config :isafe, Isafe.Web.Endpoint,
http: [port: 80],
https: [port: 443,
otp_app: :isafe,
keyfile: "/etc/letsencrypt/live/yourdomain.com/privkey.pem",
cacertfile: "/etc/letsencrypt/live/yourdomain.com/chain.pem",
certfile: "/etc/letsencrypt/live/yourdomain.com/cert.pem"]

Restart your server and TADA, your are running HTTPS (note that the screenshot is from a test domain, you will want to try your domain).

Last piece of the puzzle is to configure your application to always use HTTPS. To do this, you will need to configure force_ssl, and ensure that your url’s host is configured properly. Below is one of the many ways based on Plug.SSL configurations.

config :isafe, Isafe.Endpoint,
url: [host: "yourdomain.com", port: 443],
http: [port: 80],
force_ssl: [hsts: true],
https: [port: 443,
otp_app: :isafe,
keyfile: "/etc/letsencrypt/live/yourdomain.com/privkey.pem",
cacertfile: "/etc/letsencrypt/live/yourdomain.com/chain.pem",
certfile: "/etc/letsencrypt/live/yourdomain.com/cert.pem"]

Now your site is being served up only through SSL directly through Phoenix (no Nginx required).

Happy HTTPS.

--

--

Andrew Forward

Back to PHP and #elixir; flirting with #Elm. TDD infected since jUnit 2.1, former cheesemaker, and working at #crossfit