Heroku managed to significantly lower the bar when it comes to deploying applications and integrating them with third-parties through various add-ons. It can be argued, but in my opinion optimizing platform to be easy at the entry-level made it much harder to do some more advanced setups. It’s strange but for quite a long time of using Heroku and feeling comfortable with it I’ve never had to configure it from A to Z. I was treating Heroku as some kind of rapid prototyping environment and finally migrating it to more dedicated solutions. That said, for one of the projects Heroku turned out to be a great fit and it made sense to keep it that way. So, now I only have to set up a root domain.
Add domain on Heroku
Firstly, we have to make Heroku aware which domain is going to be assigned to our application.
I’m going to focus on common scenario, adding a root domain and www
subdomain for the sake of
showing the differences.
heroku domains:add example.com
heroku domains:add www.example.com
Remember to specify correct remote in case of using more than one. I’m used to use master branch as production and staging branch as... staging.
heroku domains:add example.com -r production
heroku domains:add staging.example.com -r staging
You can see your current config by calling heroku domains
without any additional parameters.
$ heroku domains
Domain Name DNS Target
─────────────── ──────────────────────────────────
example.com example.com.herokudns.com
www.example.com www.example.com.herokudns.com
Configure DNS
Next thing to take care of is DNS configuration. It’s super straightforward for subdomain as it
only requires setting CNAME
record.
CNAME www.example.com www.example.com.herokudns.com
Things get more tricky when it comes to the root domain. If you goggled this post you probably know what’s the problem. Heroku doesn’t provide static IP address for your application or to be more precise, Heroku claims IP can be changed to provide maximum uptime so you shouldn’t rely on IP address.
At this point is should be clear that using A
record isn't reliable in the long run, although you
can host example.com
to check you app’s server IP. So, what’s the alternative to A
record?
You should use ALIAS
/ANAME
records depends on your DNS provider. The problem is that most of
the domain registers like GoDaddy doesn’t allow you to set such record from your domain management
panel. I’m not sure why is that way (I can guess, $$$). At this point I’d like to come up with
something better than serving my app from www
subdomain and redirect to www.*
from root domain.
That's the best you can do if you don't want to swich DNS server.
I’m going to use DNSimple as I like their clean UI and affordable pricing, but you will be good with any popular DNS provider. To switch to DNSimple you have to configure your domain’s DNS Servers to:
ns1.dnsimple.com
ns2.dnsimple.com
ns3.dnsimple.com
ns4.dnsimple.com
Those may change in the future (e.g. for new domains), so make sure those are ment for you.
Now, when you are waiting for a domain to switch to different DNS server you can add DNS entries. Example configuration can look like this:
Type Name Content
CNAME www.example.com www.example.com.herokudns.com
ALIAS example.com example.com.herokudns.com
It may take up to 24h for changes to take place but it’s more like 6h from my experience. You can check the progress on cachecheck.opendns.com. Keep in mind that setting your Heroku domain instead of Heroku’s DNS would work but it fail when you setup SSL.
Don't forget about cloning MX
records so you keep your email working if you have any.
Obtain certificate
Update: Heroku introduced Automated Certificate Management. Service is available on Hobby and Professional dynos. ACM uses Let’s Encrypt to automatically generate and renew certificates for custom domains. You can skip to Enforce HTTPS if you decide to use it.
When it comes to certificates, we have two options: buy one or generate it with Let’s Encrypt. What is better for you depends on your needs but if you just want to have encrypted connection, use HTTP/2 or Service Workers then you should be fine with free certificate from Let’s Encrypt.
The easiest way to generate Let’s Encrypt certificate is using certbot. Unfortunately you won’t be able to perform auto-authorization as you can’t(?) run certbot on Heroku. Unlike on your own server with SSH access.
Install letsencrypt:
sudo apt-get install letsencrypt
or in case you are using macOS:
brew install certbot
Generate certificate, use manual
flag. Running letsencrypt may require sudo
.
letsencrypt certonly --manual --email [email protected] -d example.com -d www.example.com
Follow further instructions and verify your ownership of the domain to complete the process.
Install certificate
To upload SSL certificate we are going to use Heroku SSL addon which is enabled by default on all paid dynos. There is also paid alternative, SSL Endpoint, which is only relevant if you care about Firefox 2 or Internet Explorer 7, due to lack of SNI support, ouch!
heroku certs:add /etc/letsencrypt/live/example.com/fullchain.pem /etc/letsencrypt/live/example.com/privkey.pem --app example-app
Make sure you've changed the path to match your domain. The path where certifiicate is saved should be included in the success message you saw after generating it.
After certificate is added you see instructions how to configure DNS, but if you followed previous steps you should be already fine.
Enforce HTTPS
On Heroku SSL termination happens at load balancer, so your app has to check x-forwarded-proto
header to determine either you are serving through HTTP or HTTPS.
Here’s an example of middleware which redirects requests to https.
function enforceHttps(req, res, next) {
if (!req.secure &&
req.get("x-forwarded-proto") !== "https" &&
process.env.NODE_ENV === "production") {
res.redirect(301, `https://${req.get("host")}${req.url}`);
} else {
next();
}
}
app.use(enforceHttps);
Photo by rawpixel on Unsplash.