Adding A Custom Domain to A Heroku Free Dyno App with HTTPS, Cloudflare, and Auth0

Tutorial

I spent a few days solving this problem: given a Node application being hosted on a free Heroku dyno, how do you make it available on your own domain and have it served over HTTPS?

For additional context, my domain is managed by Google Domains and uses Auth0 for user authentication. To add even more complication, until now the domain was serving a static Hugo site over on Netlify.

This step-by-step is several days of research and headaches condensed into one (hopefully) easy guide.

TL;DR:

You can’t. I got 97% of the way there (as described below) before running into the final paywall. To summarize the solution: pay for a Hobby dyno on Heroku and let them set up SSL.

Easier Solutions

Before diving into the below, here are some easier solutions I can share with the benefit of hindsight:

  • Pay Heroku $9/month: all of the benefits below are included, and you don’t need to introduce yet another 3rd party into your app environment. (update in hindsight: maybe not true since Cloudflare—the 3rd party—still seems to be the only free DNS with CNAME flattening)
  • Start with a fresh domain: expect the process to go on longer than you might hope if the domain has already been in use, especially if it was via a different host server.
  • Be ok with HTTP: especially for smaller demo projects, consider whether you actually need the app served over HTTPS. If you can live with that, you can accomplish this without Cloudflare via Heroku, or paying Heroku.

Step 0 - Get A Domain

You can use a service like Google Domains, AWS Route 53, Namecheap, or one of the hundreds of domain providers out there. Or you can use a domain you already have access to.

The main thing is you need to be able to access the domain management settings, and particularly to change the name servers.

If you’re repurposing an existing domain, do what you need to do to tell that service to stop pointing traffic to the domain.

Step 1 - Set Up Cloudflare

Cloudflare will be the DNS provider because:

  • Domain registrar (Google Domains for me) doesn’t allow A or ANAME records on a domain root (i.e. example.com), which is required for setting up a custom domain on a Heroku app.
  • Other standalone DNS providers charge quite a bit by comparison
  • Cloudflare offers DNS services, SSL certificates, and CNAME flattening (works instead of ANAME records) for free.

Here are the steps to get started:

  • Visit cloudflare.com and create an account if you don’t already have one.
  • Add a site to your account—provide the domain name (the free plan works just fine). Cloudflare will look up the existing DNS records.
  • If you are re-using an existing domain, remove any DNS records that will point your site anywhere other than Heroku. You can see what these are by copy/pasting the IP addresses into a web browser…you’ll likely get an error that references your current name servers. In my case I (took a screenshot first in case I needed to add them back later and then…) deleted all of the records listed in Cloudflare.
  • Continue on to the Cloudflare Overview page and follow the instructions there…
    • Go to your domain registrar settings and remove the name servers Cloudflare listed.
    • Add the two name servers Cloudflare provides…save all changes.
  • Continue on to the following steps to keep things moving, but for now in Cloudflare…
  • Wait. It took just over 53 hours for all of these changes to propagate for me. In a command line window you can run curl -I www.example.com to see who is serving your domain; when you see ‘cloudflare’ or ‘cf’ referenced in the result, the wait is over. Examples:
Set-Cookie: __cfduid=...
...
Server: cloudflare
CF-RAY: ...

Step 2 - Add The Custom Domain in Heroku

  • Back in the Heroku dashboard, open the Settings tab and scroll down to Domains and certificates.
  • Click Add domain and add two: your root domain (example.com) and a www subdomain (www.example.com). Heroku will display two different DNS targets (xxxxx.herokudns.com). Keep these handy, you’ll need them.

Step 3 - Configure Cloudflare

  • Back in the Cloudflare dashboard, open the DNS section. Add two CNAME records:
    • CNAME — www — DNS target on www from Heroku (step 2) — Automatic TTL
    • CNAME — @ — DNS target on root from Heroku (step 2) — Automatic TTL
  • Scroll down to CNAME Flattening and confirm that Flatten CNAME at root is selected.
  • In the Cloudflare dashboard, open the Crypto section. The first section is SSL; once your certificate has been issued (may take 24+ hours on a free account) it will say Universal SSL • Status Active Certificate. Select Flexible in the dropdown menu. This page explains the different modes. (Hindsight addendum: when you pay Heroku and SSL is included, select ‘full’ instead of flexible)
    • Note, if you want full encryption from end-to-end, more steps are required.
  • Scroll down to Always Use HTTPS and ensure that it’s in On mode.

Once again, some waiting might be required while these changes propagate across the globe.

Step 4 - Update Auth0

Now that the domain for your site has changed, you’ll need to make some small adjustments for Auth0 to continue working:

  • In the Auth0 dashboard, open the Applications tab and click the settings gear icon for the relevant app.
  • Under Allowed Callback URLs, add the root and subdomain versions of your URL including https: so https://www.example.com/callback, https://example.com/callback
  • Add these URLs without the /callback path to Allowed Web Origins and Allowed Logout URLs.

ARRRGGGHHHH THE FINAL ROAD BLOCK!!!

All looked well and good until I tried logging in to the app, and I was hit with the TOO MANY REDIRECTS issue again. Last time I solved this by telling Express to trust the proxy. This time there’s an additional party involved, and it’s not guaranteeing the cookie is served securely.

While step 3 above says to set the SSL to ‘Flexible,’ this article suggested why there’s an issue here. Here are some more things I used to try & fix this:

Additional Considerations

  1. I think it’s worth it to encrypt the traffic between Heroku and Cloudflare so that you can use ‘Full’ instead of ‘Flexible’ SSL protection. This article is helpful in understanding why. (Hindsight addendum: this is MANDATORY if Auth0 is in the picture!!! It won’t work without doing this, hence the need to pay)

  2. Instead of creating two DNS targets, maybe it’s possible to create one for the www subdomain, and forward all requests from the root domain to www. I didn’t try this.

Resources

When things went wrong here and there throughout this process, bits and pieces from several resources helped:

Long Story Short

Rather than completing this post with information that didn’t ultimately work, here’s what happened:

  • I got as far as the site working on an HTTPS url with flexible SSL except any pages that required an Auth0 login. That means Cloudflare could successfully serve the site, and all the traffic between Cloudflare and the client was being encrypted; however since the traffic between Heroku and Cloudflare wasn’t encrypted, Auth0 threw a fit and stopped the user from logging in successfully.
  • I went through the post above about adding my own certificate to encrypt the traffic between Heroku and Cloudflare, but this is a paid Heroku feature. The posts don’t explicitly say this.
  • I upgraded my Heroku dyno to paid and had fully encrypted SSL in about 20 seconds for the cost of $7/month.

Lesson learned!