Set up Heroku SSL using Let's Encrypt
You built your first app on Heroku and want to set up a custom domain. Everything is going well until you realize you need to serve your website over https. Unfortunately Heroku does not integrate with LE (yet) so you'll have to do some manual labor. Lucky for you, it's easy to do so!
How Let's Encrypt Verifies Your Domain
LE will make a GET request to an endpoint like https://YOURDOMAIN.com/.well-known/acme-challenge/KEY
.
It will expect this endpoint to respond with KEY.TOKEN
. The response body should only contain plain text (no formatting, no spaces).
Setting up The Endpoint
I'll be using Node.js + Express in this example, but you can do the same in your language/framework of choice. Place this near the top of where your Express app is instantiated.
if (process.env.LE_URL && process.env.LE_CONTENT) {
app.get(process.env.LE_URL, function(req, res) {
return res.send(process.env.LE_CONTENT)
});
}
Notice how we are not hard-coding the .well-known/acme-challenge/
part of the URL as that may change.
Installing Certbot
Certbot is the official LE client developed by EFF.
The easiest way to install it on a Mac is using homebrew:
brew install certbot
On Ubuntu:
sudo apt-get install certbot
Using Certbot
We will be running it in manual mode, since we don't want it to install the certificates on our development machine.
certbot certonly --manual
This will probably require sudo privileges, but you can set --logs-dir
, --config-dir
, and --work-dir
to writeable paths.
Follow the prompts until it displays something like this:
Make sure your web server displays the following content at https://YOURDOMAIN.com/.well-known/acme-challenge/1a2b3c before continuing:
1a2b3c.9z8x7y
Press ENTER to continue.
Setting Environment Variables in Heroku
Now that our endpoint is set up, all we have to do is set the config vars in Heroku.
heroku config:set LE_URL=/.well-known/acme-challenge/1a2b3c LE_CONTENT=1a2b3c.9z8x7y
You can also do this in the app settings page.
Verifying Your Domain
Once your endpoint is pushed to Heroku and the config vars are set, press enter
/return
in the Certbot terminal prompt to have LE send the GET request and verify your domain.
Installing the Certificates
If all goes well, your certificates should be issued and placed in /etc/letsencrypt
.
sudo tree /etc/letsencrypt
Should look something like this:
/etc/letsencrypt
├── accounts
│ └── acme-v01.api.letsencrypt.org
│ └── directory
│ └── b2aa2f55bd4h3c65f2aac2bbb522d0c
│ ├── meta.json
│ ├── private_key.json
│ └── regr.json
├── archive
│ └── YOURDOMAIN.com
│ ├── cert1.pem
│ ├── chain1.pem
│ ├── fullchain1.pem
│ └── privkey1.pem
├── csr
│ └── 0000_csr-certbot.pem
├── keys
│ └── 0000_key-certbot.pem
├── live
│ └── YOURDOMAIN.com
│ ├── cert.pem -> ../../archive/YOURDOMAIN.com/cert1.pem
│ ├── chain.pem -> ../../archive/YOURDOMAIN.com/chain1.pem
│ ├── fullchain.pem -> ../../archive/YOURDOMAIN.com/fullchain1.pem
│ └── privkey.pem -> ../../archive/YOURDOMAIN.com/privkey1.pem
└── renewal
└── YOURDOMAIN.com.conf
To add them to heroku:
heroku certs:add /etc/letsencrypt/live/YOURDOMAIN.com/fullchain.pem /etc/letsencrypt/live/YOURDOMAIN.com/privkey.pem
HTTPS Only
To redirect users to the https
version of your site, you can use the x-forwarded-proto
header set by Heroku.
if (process.env.NODE_ENV === 'production') {
app.use(function(req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https' && req.path !== process.env.LE_URL) {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
return next();
});
}