If you followed along in our recent three-parter all about Propshaft, static assets, and CDNs, you might be considering integrating a reverse-proxy CDN into your own app! As we mentioned in part two, that’s exactly what we recommend and follow for all of our own fresh applications. But if you’ve ever actually tried to integrate Cloudflare with Heroku, you might’ve run into the most common snag between the two. SSL certificates and proper HTTPS configuration.
So let’s explore and figure this out. We want to understand what’s actually happening here, how it ought to work, and how we actually set it up in practice (it’s super easy!)
What’s Actually Happening
Let’s rewind just a smidge. In “How CDNs Work” we walked out what actually happens when you have a properly configured, running CDN. The CDN intercepts requests for static assets and serves them on behalf of your application after loading them the first time. We used this image in that article:
Then we talked about how reverse-proxy CDNs actually sit between every requests to your application, only intercepting them and serving them directly when they’re requests for static assets. We showed this diagram there, too:
And both of these tease around a point that we need to be more explicit about in this article to better understand the interaction. When a request hits Cloudflare for any proxied domain (that is, the ‘orange cloud’ is enabled in the Cloudflare DNS record), two separate web requests actually occur:
In the upper section, “Without Cloudflare”, we see that a request being made to the site / app resolves directly to, and is served by, the web dyno. As we’ve mentioned before, this is a totally fine setup for a small or proof-of-concept application, and it actually means your SSL setup will be simpler. In fact, if you’re not using any reverse-proxy CDN and your application’s web domain is CNAME’d to your ___.herokudns.com target, you can use Heroku’s automatic SSL management and be done:
But that’s not what we’re here for! That would mean we have no CDN in front of our application at all — which simply won’t do! We’re here for Heroku plus Cloudflare. So we need to go a little deeper and understand what’s happening in that “With Cloudflare” track from the image above.
The first key point to understand is that when Cloudflare is actively proxying your domain, the DNS target for that domain does not directly resolve to your dynos!
Here we see that directly querying the DNS system for judoscale.com resolves to the IP address 104.26.1.154. But what system / company / service owns that IP address? Let’s check.
WHOIS 104.26.1.154
NetRange: 104.16.0.0 - 104.31.255.255
CIDR: 104.16.0.0/12
NetName: CLOUDFLARENET
NetHandle: NET-104-16-0-0-1
Parent: NET104 (NET-104-0-0-0-0)
NetType: Direct Allocation
OriginAS: AS13335
Organization: Cloudflare, Inc. (CLOUD14)
RegDate: 2014-03-28
Updated: 2024-09-04
Comment: All Cloudflare abuse reporting can be done via https://www.cloudflare.com/abuse
Comment: Geofeed: https://api.cloudflare.com/local-ip-ranges.csv
Cloudflare! So when we hit judoscale.com, we’re actually connecting to a Cloudflare IP address, and thus, a Cloudflare server. Not a Heroku server.
On the other hand, if we query the DNS system for our __.herokuapp.com domain we see that it resolves to 18.208.60.216, which we can also check ownership of:
While not as clear as “Cloudflare owns it!”, we know that Heroku uses AWS servers under the hood, so it’s clear enough to say that this domain resolves directly to our Heroku web dyno. And indeed, if we hit this domain with our web browser we do see our site!
This duality illustrates a key point in this paradigm. When using a reverse proxy CDN the end-user never connects directly to your web dynos. That’s ultimately why there are two separate web requests that occur each time an end-user requests some HTML from your site/app. Since an end-user will always connect to Cloudflare’s servers for any of their requests, Cloudflare then needs to request content from our Heroku dyno on the user’s behalf if the request is for something Cloudflare can’t serve itself.
Now, the reality is that this setup is fine and as-designed. Cloudflare’s network is extremely fast and built specifically for this architecture. It’s rare that we ever need to unravel this complexity and consider the dual requests…. But SSL certificates is indeed one of those rare cases.
Remember that SSL certificates essentially provide a third-party confirmation that the server presenting the certificate is indeed authorized to represent the domain you’re requesting (e.g. judoscale.com). But the tricky bit here is that in order to generate an SSL certificate, you need to prove both that you control the domain and that the server requesting the certificate is authorized to serve traffic for it. But that’s just it — we don’t control Cloudflare’s servers directly, and they’re the ones serving our domain!
And, to clarify, let’s put this back in the context of dual-requests.
Since we have two different requests between three separate parties, we actually need to worry about two different SSL certificates.
How It Ought To Work
First, we want Request 1 to be secure. That is, the user should be navigating to a https:// and, ideally, Cloudflare should force requests to http:// over to https://. This is good for general user security and should be a baseline for any site / app. That means that Cloudflare needs to have an SSL certificate which covers our domain and that it serves freely.
Second, we want Request 2 to be secure. This is actually just as important as Request 1 but overlooked way too often. Let me put it this way — if a user is sending personal information to your app and it looks secure from their perspective (browser 🔒 symbol), it’s our duty to make sure that the security is upheld all the way to our origin server so that we’re not lying to our users. “Well it’s secure to our CDN but then plain-text from the CDN to our servers” is not what you want to tell your users should the issue ever arise.
Cloudflare calls this setup “Full” encryption, and it should be the standard for all web applications:
That is, both request 1 and 2 must be covered by verified, trusted certificates.
👀 Note
Just for context, Cloudflare offers a few different levels of SSL/TLS Encryption:
And each of these tiers slightly modifies how Cloudflare behaves when interacting with the end-user/site visitor, itself, and our origin servers. The top three are worth understanding in better detail — Full, Full (Strict), and Strict. The latter is only available to Cloudflare Enterprise customers (💸) and is the best choice, but as long as you’ve got the “Always Use HTTPS” switch enabled (see TIP below), Full (Strict) will have the same benefit.
Importantly, the difference between Full and Full (Strict) boils down to which certificate your origin server gives back to Cloudflare when Cloudflare attempts to setup a secure connection to your server. Full means that, as long as your origin server returns an SSL cert, it will use that cert for a secure connection. This is a good first step, but the whole point of an SSL certificate is to validate it against a third party (the party that issued the cert, ideally).
That’s precisely what Full (Strict) does — it requires a cert from your origin server and it validates that cert to ensure that it’s valid with the original issuing party. It ensures that the server acting as your origin isn’t giving back a random, unknown certificate. Instead, that it’s giving back a valid, legitimate certificate. The best way to get this working on Heroku is to setup your origin server with a certificate that Cloudflare itself generates! That way Cloudflare can immediately validate the cert.
So let’s talk about how we do that… and it’s not as hard as it sounds!
Putting It In Practice
The good news here is that getting all of these certs and layers going is actually quite simple.
First, the cert for Request 1 — between the user’s browser and Cloudflare. Great news here: Cloudflare automatically sets up a certificate for your domain when you enable the Cloudflare proxy (orange cloud) and automatically serves it. Cloudflare calls these certificates “Edge Certificates” and every plan (yes, free plans!) includes a Universal certificate that covers the root domain and all single-layer subdomains (*.example.com).
And yes, that’s as simple as it sounds! When you add your domain in Cloudflare, Cloudflare handles all the work to get a cert running for Request 1.
✅ Tip
While we’re at it (call it step 1.5), we should configure Cloudflare to force all incoming traffic to HTTPS rather than HTTP. Again, this has become the standard for web traffic over the last decade and, luckily for us, is as simple as toggling a switch.
Head over to the SSL/TLS -> Edge Certificates page in the Cloudflare domain dashboard then scroll down until you see the Always Use HTTPS toggle. Just turn it on!
So now we’re here:
And this is where most folks get tripped up, bail, and set their Cloudflare SSL settings to “Flexible” — not actually using HTTPS at all between Cloudflare and Heroku! This is bad! Don’t do this!
Instead, follow these few steps.
First, head over to the “Origin Server” section of the SSL/TLS panel in the Cloudflare Dashboard for your domain. Then click “Create Certificate”.
Just go with the defaults here, they’re all fine. And yes, a 15-year expiration is fine here! This is going to be a very specific certificate that your Heroku dyno will serve to Cloudflare (and Cloudflare only) so it doesn’t need to be cycled with any real frequency.
Once you click “Create” and the cert is setup by Cloudflare, you’ll get this screen with some special key strings. Don’t navigate away from this screen. You’ll need these values and can’t ever access the second one (Private Key) again once you close the page!
So, in a different browser tab, head over to your Heroku app’s Settings tab and scroll down to the SSL Certificates section. Hit “Add certificate”
The “Configure Certificate” overlay panel should pop up and give you the option to specify a name for the cert (we usually use “Cloudflare Origin Cert” to help us remember where it came from) as well as a link to switch the input from a file drag-and-drop to a plain text field for pasting.
Finally, we just need to copy and paste the certificate information and private key that Cloudflare generated for us over into the Heroku panel.
Hit “Next”, click the check-box for your application’s domain, and save that thing!
Now your Heroku SSL settings should look like this:
Which essentially means that when somebody requests “Judoscale.com” from Heroku’s servers, that’s the certificate that will be served! Now, remember that in the public DNS system, judoscale.com actually resolves to Cloudflare. But when Cloudflare requests our site from Heroku (Request 2), it requests the same domain from Heroku — judoscale.com. And thus, Heroku will serve the Cloudflare certificate back to Cloudflare! This is how Cloudflare validates that the origin is both correct and secure. That makes our diagram now look like this:
But there’s one last step! Now that our Heroku Web Dyno can serve requests to Cloudflare securely, we need to configure Cloudflare to only allow that level of security — no less! Let’s hop into our Cloudflare Dashboard one last time, head to SSL/TLS -> Overview and set our configuration to “Full (Strict)”.
And that’s it! Our app should now be serving fully secured requests on our domain and Cloudflare should be communicating securely to our origin server without issue!
👀 Note
One last quick aside here to clarify a few thoughts about the ‘orange cloud’ control. For any given DNS record in a Cloudflare site, that record can be “Proxied” (orange cloud on) or “DNS only” (orange cloud off). When the record is set to “DNS only” (gray), the entire Cloudflare platform (tooling, features, etc.) is unavailable for that record. When gray, Cloudflare is essentially a simple DNS provider and simply resolves the DNS record in a traditional manner. This can be helpful for certain setups or specific use-cases where you really do need your domain to resolve to the hosting IP address directly, but it side-steps the point of using Cloudflare in the first place.
In contrast, activating the orange cloud (“Proxied”) is what actually sets up the Cloudflare-as-middle-man architecture we just covered. When Proxied, Cloudflare routes all requests through their systems and activates all features and benefits of the CDN.
Reverse-proxy CDN activated
No CDN, simple DNS resolver
Tl;dr:
For those of you that just want to scroll to the bottom, here’s the ultra-short version:
Add your domain to Cloudflare, enable proxying (orange cloud)
Generate an Origin Certificate for your app in Cloudflare’s dashboard
Add that origin cert to the Heroku app
Make sure the domain (same one) is already added to the Heroku app
Set your Cloudflare settings to Force HTTPS (SSL/TLS->“Edge Certificates” page then scroll down) and set your encryption configuration to “Full (Strict)” (SSL/TLS->“Overview” page then “Configure”)
Put on your sunglasses, you SSL master, you! 😎
We hope this clears some things up for everyone and gives some insight into how all this plumbing works! ✌️