Heroku SSL Revisited (2025 Edition)

Adam McCrea
@adamlogicLast year we published Cloudflare + Heroku SSL / Certificates Explained, in which Jon Sully masterfully walked through exactly how SSL works when using reverse-proxy CDN like Cloudflare, and how to set it up correctly with Heroku.
Jon’s (and my) advice at the time was:
- 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 as “Custom SSL”.
- Set your Cloudflare SSL to “Full (strict)”.
This setup still—without a doubt—works great.
But something kept nagging at me. Heroku provides automatic SSL management (via Let’s Encrypt) for free, and I feel like there should be a Good Reason why we advise against using that golden path.
Here’s Jon’s explanation:
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 the theory was that the Let’s Encrypt SSL cert generated by Heroku wouldn’t validate with Cloudflare’s “Full (strict)” SSL mode.
I had two questions:
- Why is “strict” so important—is there a real risk involved without it, or is it just security theater?
- Why would a Let’s Encrypt SSL cert fail to validate in strict mode?
Let’s dive into each of these.
SSL strict mode: does it even matter?
As Jon laid out, both “Full” and “Full (strict)” are encrypted end-to-end, but strict mode validates the certificate at the origin (Heroku in this case) while non-strict mode would not. This means that if a bad actor could intercept requests between Cloudflare and Heroku, they could use a self-generated certificate and we’d never know they were there.
But seriously, how could someone realistically intercept traffic between Cloudflare and Heroku? Cloudflare-to-Heroku communication happens across major cloud provider backbone networks, making traffic interception via traditional methods (e.g., DNS spoofing, MITM) exceedingly difficult. Such an attack would typically require compromising critical infrastructure or sophisticated nation-state actors.
So in theory yes, it is possible, but the odds of someone intercepting traffic between Cloudflare and Heroku are super low. We’re talking major internet infrastructure here, not your average coffee-shop Wi-Fi. If you’ve made it this far through this article, chances are your security requirements don’t rise to the level of nation-state actors.
BUT… You know what’s a more realistic scenario? A security audit!
If your app ever requires SOC 2 certification, PCI compliance, or even a basic penetration test, this is the kind of thing that will get flagged. As my friend Ben Sheldon puts it:
yes, “nation state actor” and “grumpy security compliance officer” occupy the same threat space
Nailed it.
So if I’m being honest, I still think SSL strict mode qualifies as security theater, but I recognize that security theater is just part of the game. And since it’s very little effort to make SSL strict mode work, there’s no good reason to skip it.
But in fact, we’re about to see whether strict mode requires any extra effort at all.
Heroku ACM and SSL strict mode
Heroku ACM is their “automatic certificate management” feature that uses Let’s Encrypt behind the scenes to generate SSL certificates for your apps. Like most features on Heroku, it’s super seamless with one click of a button.
But historically, Heroku ACM hasn’t played well with Cloudflare’s strict SSL mode. Here’s one of Heroku’s help docs:
Apps using CloudFlare [in “Full (strict)” mode], a custom domain with an herokudns.com endpoint and no custom SSL certificate will see a “Error 525 - SSL handshake failed” message.
Indeed, this was our experience as well, and it’s why we’ve long advised creating an origin cert in Cloudflare and installing it via “custom SSL” in Heroku.
But I still didn’t understand enough of the verification process to grok WHY the cert wouldn’t validate in strict mode. Let’s Encrypt certs should be third-party verifiable, right??
I decided to stop thinking and just try it.
Well how about that… it just works! I set up ACM and a custom domain in Heroku, created my CNAME in Cloudflare with “Full (strict)” SSL, and it… just… works.
In fact, Heroku has yet another help doc that confirms this (and contradicts their other doc):
With SSL Mode “Full (strict)”, Cloudflare verifies the domain using the TLS certificate provided with ACM. Client programs accessing the custom domain see the TLS certificate provided by Cloudflare.
But then they go on to say:
Note there is little need to have an SSL certificate on Heroku if you use Cloudflare - instead, you should consider generating a Cloudflare Origin SSL certificate and turning off ACM.
What?? Why??? I know it’s not much work to create an origin cert in Cloudflare and set up custom SSL in Heroku, but it’s much easier to click one button in Heroku to enable ACM.
My new recommendation for Heroku SSL
Now that Heroku automatic certificates play nice with Cloudflare, I see no reason to create origin certs in Cloudflare anymore. For me, it’s all Heroku ACM for the foreseeable future, regardless of what their docs say—their docs can’t agree with each other anyway. 😅
Bottom line: Unless you enjoy extra clicks for the sake of nostalgia, skip the custom origin certs. Just flip on Heroku ACM, set Cloudflare to “Full (strict),” and call it done. It’s simpler, fully secure, auditor-friendly, and—best of all—you can stop wondering if you’ve missed something.