Heroku doesn’t offer custom domains with HTTPS, since I just wanted to test something quickly I didn’t want to upgrade to another plan with them but still wanted to use HTTPS. Because it’s 2021.

If you host your domain/DNS on Cloudflare, well, there’s Cloudflare Workers to help!

It’s possible to do a header override with Cloudflare Workers to the origin server. To make this happen, just create a new Cloudflare Worker in the dashboard or follow this link and copy and paste the contents from below in the editor field. Just edit the HEROKU_TARGET variable to match your own app.

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
})

const HEROKU_TARGET = "https://abcd-defg-1337.herokuapp.com"

async function handleRequest(request) {
  return fetch(HEROKU_TARGET + new URL(request.url).pathname, request);
}

Afterwards, go into the Cloudflare Dashboard for the domain you want to use with Heroku (or any other app/site) and add a custom route to a worker. The route should probably look like this:

https://example.com/*

Now, if you open up your page, the request will go through a Cloudflare Worker, it’ll fetch the contents from the HEROKU_TARGET URL and present it back to you under your own domain name. And it’s all secure.

a slightly more advanced version

Here’s another version of the above but more custom to my use-case. The application I’m testing on heroku responds with redirects from time to time, depending on what’s being executed. With the solution from above, I’d be redirected back to the heroku domain since this was part of the location header.

In the example below I’m looking for the location header and if one is available, I’ll just do a search and replace between the heroku domain and my domain. See the variables HEROKU_TARGET and ORIGIN_URL

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
})

const HEROKU_TARGET = "https://abcd-defg-1337.herokuapp.com"
const ORIGIN_URL = "https://my-custom-domain.com"

async function handleRequest(request) {
  // Make the headers mutable by re-constructing the Request.
  request = new Request(request)

  // URL is set up to respond with dummy HTML
  let response = await fetch(HEROKU_TARGET + new URL(request.url).pathname, request)

  // Make the headers mutable by re-constructing the Response.
  response = new Response(response.body, response)
  if(response.headers.get('location')) {
    let locationHeader = response.headers.get('location')
    response.headers.set("location", locationHeader.replace(HEROKU_TARGET, ORIGIN_URL))
  }
  return response
}