How to set up SSL with Let’s Encrypt on Heroku for free

Edit: Heroku now provides Let’s Encrypt SSL for Hobby dynos for free out of the box. Just enable it in your app’s settings. Things described in this guide still work, but it’s no longer needed to do all these steps.

It’s possible to configure a SSL certificate using various Heroku plugins, but this guide is for Heroku SNI, which is free, if you have at least Hobby dyno (7$/mo).

Heroku SNI and its configuration is described in their documentation, but I want this guide to cover all steps. You shouldn’t need to look elsewhere.

Disclaimer: I consider myself rather a designer than a developer, so this may not be the perfect guide, but this is how I did it and it works 🤘.

I am using Ruby on Rails for the backend, but this can be easily used for any other framework/language.

Setting this up is a multi-step process. I will assume that you plan to use a custom domain for you project. You need to:

  1. Turn on Heroku SSL
  2. Add your custom domain to your Heroku app
  3. Generate the certificate on your machine
  4. Make your app respond to certbot
  5. Upload the certificate to Heroku
  6. Set up your app for SSL

Let’s get our hands dirty.

1. Turn on Heroku SSL

At the time of writing this, Heroku SSL is in beta, which means you have to specifically request this feature and wait for approval. They say it may take a while, but mine was ready to roll in a minute.

In order to start using SNI SSL on your application, you first need to enable the labs flag and install the CLI plugin.

Update: Heroku released a stable version, so no labs flag needed! I’ll leave it here for a future reference though.

On your machine:

// $ heroku labs:enable http-sni -a your-app
$ heroku plugins:install heroku-certs

If all goes well, you can now find a “SSL Certificates” section in the Heroku settings of your app. Looks like this:

SSL Certificates section of your Heroku app settings

2. Add your custom domain to your app

We don’t have the certificate yet, so scroll down a little and let’s add your custom domain. Heroku should then display your.domain.herokudns.com as DNS Target. So go on and point ANAME records of your domain to this your.domain.herokudns.com target.

DNS changes always take some time to come through. We can use that time to…

3. Generate the certificate on your machine

If this is your first time with Let’s Encrypt (like in my case), you need to install certbot on your computer to generate certificates.

If you’re on mac, it’s really simple:

$ brew install certbot

For other systems, check https://certbot.eff.org/.

With your certbot installed, fire it up:

$ sudo certbot certonly --manual

It will ask for your email address first. You know what to do. After that, fill in your custom domain for the project.

The process continues with one prompt, but after confirmation, you will see something like this:

Make sure your web server displays the following content at
http://your.domain/.well-known/acme-challenge/first-part-of-string-random-characters before continuing:
first-part-of-string-random-characters.-second-part-of-string-random-characters

now STOP!

Notice how the string you have to display has two parts, divided by a period. See, I don’t have the necessary knowledge, but I messed this up a few times and noticed something. The first part changes for every certificate you generate and is sent in the request. The second part stays the same and only you know it. We will use this in the next step.

Leave your terminal open in this state and switch to your code editor.

4. Make your app respond to certbot

In your config/routes.rb add this line:

# Let’s encrypt
get ‘/.well-known/acme-challenge/:id’ => ‘pages#letsencrypt’

I used my static pages controller for this, but you can use whichever you want. In your controller of choice, add this method:

def letsencrypt
render text: “#{params[:id]}.-second-part-of-string-random-characters”
end

You can store the second part of random characters in some ENV variable for extra safety. That’s it for now, publish your code.

The DNS changes should be ready by now, so your app should be accessible without issues. You will see some warnings if you try the https address though.

5. Upload the certificate to heroku

Come back to you terminal and press enter. If ends up with an error, just repeat the process, your app should be ready for any of your generated certificates.

Now, you can use Heroku’s interface in settings for this part, but I used a CLI plugin we installed in the beginning.

$ sudo heroku _certs:add /etc/letsencrypt/live/<your.domain>/fullchain.pem /etc/letsencrypt/live/<your.domain>/privkey.pem — app <your-app>

You will see some kind of confirmation and when you visit your Heroku settings now, you should see “Your certificate your.domain expires on …”.

And that’s it! Well… for the most part. When you visit you site at https://your.domain you will see some issues.

6. Set up your app for SSL

I had two issues at this part of the process:

  • the site was still available on the http address
  • assets were loading from http host

First issue can be solved simply by having this in your config/environments/production.rb:

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true

The second issue depends on how you store your assets, but don’t forget to check this, as you won’t get a green lock unless it’s all secure.

Bonus: Automate the certificate renewal

I’m sure you noticed that your certificate lasts only three months. After that, you have to upload a new one. This task can be automated, of course.

I found a little app called Sabayon, that claims to do this, but haven’t tried it yet. Check it out.

Thank you for reading, I hope this guide helped you somehow. If you have any questions, I probably won’t have the answers, but feel free to ask 😁.