16

I have a Node Express app running on Heroku that I want to encrypt with a free-of-charge SSL cert from LetsEncrypt. However, the methods I've seen require opening up ports 443 and 80 to allow the ACME process to work.

Heroku only gives you one port, and doesn't let you choose which port. So how can I use LetsEncrypt?

I spent a bunch of time figuring this out yesterday. First time in a long time there were no answers on StackOverflow for something I was trying to do!

stone
  • 8,422
  • 5
  • 54
  • 66

2 Answers2

22

Update:

Heroku now supports LetsEncrypt natively! So this workaround is no longer needed.

Instructions here:

https://devcenter.heroku.com/articles/automated-certificate-management

For new apps, you don't have to do anything, it's turned on by default. For apps created before March 21 2017, you can turn it on with this Heroku cli command: heroku certs:auto:enable

Thanks @Spain Train


Background

Ideally, LetsEncrypt allows for an automated certificate renewal process. That's harder to do on Heroku, so this answer describes how to use a manual process. Using a Heroku environment var, you'll be able to update your certs manually fairly easily going forward - no code changes.

Credit for this answer goes largely to two nice blog posts: https://medium.com/@franxyzxyz/setting-up-free-https-with-heroku-ssl-and-lets-encrypt-80cf6eac108e#.67pjxutaw
and
https://medium.com/should-designers-code/how-to-set-up-ssl-with-lets-encrypt-on-heroku-for-free-266c185630db#.ldr9wrg2j

There's a GitHub project which apparently supports automated certs updates on Heroku. I'll update this answer when I've tried it out:
https://github.com/dmathieu/sabayon

Using LetsEncrypt on Heroku with a Node Express app

Get the Express server ready:

Add this middleware to your Express app. Be sure to add it BEFORE any middleware that redirects http to https, because this endpoint must be http.

// Read the Certbot response from an environment variable; we'll set this later:

const letsEncryptReponse = process.env.CERTBOT_RESPONSE;

// Return the Let's Encrypt certbot response:
app.get('/.well-known/acme-challenge/:content', function(req, res) {
  res.send(letsEncryptReponse);
});

Create the certificate files using certbot:

  1. Start certbot: sudo certbot certonly --manual
    Enter the site url when prompted (www.example.com)
    certbot will display a Challenge Response string in the format
    xxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyyyyyyyy
    LEAVE CERTBOT WAITING IN THIS STATE. Do not press enter yet or exit.
  2. Go to the Heroku dashboard and view app settings:
    https://dashboard.heroku.com/apps/your-heroku-app-name/settings
    Under Config Variables, click 'Reveal Config Vars'
    Edit the CERTBOT_RESPONSE var's value to match the Challenge Response from step a.
  3. Wait for the heroku app to restart.
  4. Test the setting by visiting http://www.example.com/.well-known/acme-challenge/whatever
    NOTE THE HTTP, NOT HTTPS
    It should display the Challenge Response string. If this happens, go on to the next step. If not, do whatever it takes to get that URL to return the CR string before proceeding, or you will need to repeat this entire process.
  5. Return to Certbot and press Enter to continue.
    If all goes as planned, certbot will tell you everything worked and display the location of the created certs. You'll use this location in the next step. Note that you might not be able to inspect the contents of the folder due to os permissions. If in doubt, sudo ls /etc/letsencrypt/live/www.example.com to see if the files exist.

Update the Heroku instance to use the new certs:

Run heroku certs:add if your site doesn't have a cert. If updating, run heroku certs:update.
sudo heroku certs:update --app your-heroku-app-name /etc/letsencrypt/live/www.example.com/fullchain.pem /etc/letsencrypt/live/www.example.com/privkey.pem

stone
  • 8,422
  • 5
  • 54
  • 66
  • 'Add this middleware', is it really middleware or just an endpoint? – softcode Feb 03 '17 at 21:37
  • Everything in Express is middleware: http://expressjs.com/en/guide/using-middleware.html. Let's say in this case it's middleware that handles a specific endpoint. – stone Feb 03 '17 at 23:32
  • Good question - I'm not sure if you need to keep that endpoint around. I don't *think* you need to, though I didn't try removing it. Once you have the cert installed, as far as I know there is no mechanism that would somehow invalidate your cert. – stone Feb 03 '17 at 23:40
  • is there a windows solution? – jasan Feb 17 '17 at 02:08
  • Worked like a charm. Thank you. – Jibu James Mar 01 '17 at 06:57
  • If certbot tries to get the response from frontend endpoint, and we're writing it on the server side, how does this work? – AG_HIHI Nov 08 '20 at 06:48
  • @AhmedGhrib sorry, what do you mean by "we're writing it on the server side?" Also, you probably already saw this and have other reasons for asking, but just in case: you no longer need to use certbot at all for Heroku. Heroku supports SSL with LetsEncrypt natively. – stone Nov 09 '20 at 07:21
3

You can also validate your domain ownership to Let's Encrypt with DNS instead of HTTP.

With certbot, specify DNS as your preferred challenge:

sudo certbot certonly --manual --preferred-challenges dns

After a couple of prompts, certbot will tell you to deply a DNS TXT record to validate your domain:

Please deploy a DNS TXT record under the name
_acme-challenge.www.codesy.io with the following value:

CxYdvM...5WvXR0

Once this is deployed,
Press ENTER to continue

Your domain registrar probably has its own docs for deploying a TXT record. Do that, and go back to certbot and press ENTER - Let's Encrypt will check the TXT record, sign the cert, and certbot will save it for you to upload to heroku.

See my detailed blog post for more.

groovecoder
  • 1,551
  • 1
  • 17
  • 27
  • Good to know, thanks! One benefit of this option is that it avoids one of the dyno restarts. (When using my method, there's a restart when you set the env var, and another when you upload the certificate file.) Minor downside is that the process might take longer, since deploying DNS records can take 10-60 minutes sometimes. – stone Jan 05 '17 at 21:36
  • 1
    It's also nice because it works on any stack - node, python, ruby, php, whatever. – groovecoder Jan 06 '17 at 16:52
  • this didn't work for me on Heroku.. kept getting errors installing / running certbot `su: must be run from a terminal` – Erik Feb 08 '17 at 21:59
  • On what OS are you running certbot? You don't run that command on Heroku - you run it on your local machine to get the signed cert. Check my blog post for more details. And file a GitHub issue for my site (linked from my blog) if you're still having issues. – groovecoder Feb 10 '17 at 02:07