Tech used

How to setup TLS Full (Strict) between Cloudflare and Heroku with WAF

Updated 2024-04-15
Photo by FlyD on Unsplash

This guide walks you through setting up a full, end-to-end secure communication between a web browser, Cloudflare, and a Heroku app. This guide has been created after some negative events impacted the availability of this site and some domain squatting related to those events.

This guide assumes you have a domain registered with Cloudflare and a Heroku app you are looking to protect.

Table of Contents

  1. Intro
  2. Cloudflare Setup
  3. Heroku Setup
  4. Musings
  5. Acknowledgements


For certain apps, as they grow organically (or become targets of bad actors), using Cloudflare's free Web Application Firewall (WAF) is a very attractive proposition. After all, adding a WAF through Heroku itself costs more than hosting the app there in the first place for the lowest paid tier. It is possible, however, if you're paying for hosting in Heroku, that you are already making use of the Automatic Certificate Management (ACM). Using such a service from Heroku, unfortunately, is incompatible with Cloudflare's proxy DNS settings because proxying breaks the DNS checks Heroku performs at the time a certificate is being renewed. In order to ensure you and your customers enjoy a fully encrypted communication channel, and you make full use of Cloudflare's WAF, we'll need to disable the ACM on the Heroku side and create, and then upload to Heroku, an origin certificate from Cloudflare. Let's start, then, at the beginning.

Very Important Note:
Plan for a little bit of downtime for your site while this is setup. You may be able to figure out a better way than presented here to minimize the disruption.

Cloudflare Setup

Origin Server Certificates

First, log into the Cloudflare's dashboard and head over to the SSL/TLS section. Specifically, you're looking for the Origin Server link:

Once within, hit the Create Certificate button. I did not change the default settings here but you may adjust them to suit your needs. Pay close attention to the List of hostnames text as it explains how to add other hostnames to be covered by the certificate you're looking to create:

Once you create the certificate, two text boxes appear: one with the Origin Certificate and one with the Private Key. Be advised that the Private Key will not be accessible once you navigate away the page. Take a moment to save both keys in a safe place at this point:

For our purposes, we'll save them in two files locally to respective files to make it easy to move to Heroku via the CLI:

After saving the files, don't forget to check the Authenticated Origin Pulls button.

With these files created, and the setting on, let's move to setting up the DNS entries.

DNS Settings

Within the Cloudflare dashboard, move to the DNS settings. You should land in Records but, if not, move accordingly. While in here, ensure that both your CNAME entries for Zone Apex and www are proxied (get the orange icon). Being proxied enables you to use WAF's rules and leverage Bot Fight Mode:

SSL/TLS Settings

Navigate to the SSL/TLS > Overview menu and select Full (strict) from the list:

Since we have not uploaded the certificates we donwloaded to Heroku yet, at this point you will be unable to reach your site through your domain. However, you may still be able to access the site by hitting the Heroku DNS Target still.

Security Settings

One last important part of the configuration in Cloudflare is setting up the WAF to reject any connections to the Heroku app that do not come from Cloudflare itself. The reason for this is simple, as Cloudflare themselves explain:

Authenticated Origin Pulls helps ensure requests to your origin server come from the Cloudflare network, which provides an additional layer of security on top of Full or Full (strict) encryption modes.

This authentication becomes particularly important with the Cloudflare Web Application Firewall (WAF). Together with the WAF, you can make sure that all traffic is evaluated before receiving a response from your origin server.

In plain English: this ensures that comms only happen between your app and Cloudlfare -- no one else can hit directly your server and bypass the protections of the WAF.

To enable this feature, head over to Security > WAF and use the Rule Template named mTLS-enforced authentication and set it to block:

With this done, we can head over to Heroku for the next steps.

Heroku Setup

The Heroku setup happens exclusively through the CLI tools. Make sure to download and install them according to their docs.

Once you're up and running with Heroku CLI, issue the following commands:

# Command Description 1 heroku login Log in to Heroku using the CLI. 2 heroku certs:auto:disable --app=my_app Run if you already have ACM turned on for your Heroku app. Otherwise, it is not needed. 3 heroku certs:add server.crt server.key --app=my_app Run to send the certificate and the key to Heroku to be used by your app. The names of the files, in this case `server.crt` and `server.key` should match what you initially called them.

If all went well, you should see the new certificate in your Heroku dashboard:

If you had any previous certs in there from ACM, this is the time to remove them.

At this point, you should now be able to visit your site via your domain again.


  • For some strange reason, this site became part of some test script. I'm not sure why it became a target for testing but I was seeing a lot of bad traffic (~1,700 hit per month) that I decided to stop. It took me a little bit of time to figure out how to configure everything to make sure I was using Cloudflare's WAF while keeping SSL/TLS enabled throughout. After setting everything just so as this guide shows, the bad traffic has dropped significantly and the WAF reports less hits for paths that do not exist or are clearly exploitable in other tech stacks.

  • Along those same lines, I had misconfigured Heroku to only serve the app through the www CNAME, likely because I did not read through the entire Custom Domain guide. This allowed someone to deploy an entirely separate website at the apex domain from within Heroku to serve garbage. My Google Search Console numbers went through the roof for a small period of time while I regained control of the CNAME. If you end up having the same issue, be sure to request a Domain Release from Heroku using this tool.