Getting Cloudflare, CloudFront + S3 to cooperate over (strict) SSL

Spoiler alert: you do NOT need to provision a custom certificate

Recently I found myself serving a dynamic Heroku app (app.mydomain.com) and a static S3 website (www.mydomain.com) from the same domain, which I manage through CloudFlare. Getting these services to cooperate over (free) SSL proved more difficult than anticipated.

Some background

Typically, I configure S3-based sites to use Cloudflare'sFlexible SSL, because Amazon doesn’t automatically provide HTTPS access to statically hosted buckets. However, the secure login pattern of my Heroku app required Cloudflare’s SSL to be set toFull (seemingly a requirement of the Rails gem Devise—another story for another time).

In order for S3-based sites to be compatible with Cloudflare’s Full SSL setting it seemed necessary to deliver them through another Amazon service: CloudFront. CloudFront is best know as a CDN but it also provides a single point of entry and configuration for a web asset, providing built-in certificate management.

I found this approach surprisingly complicated and poorly documented, so I’m posting the steps I believe are required in order to get this setup working properly.

This approach assumes a custom domain connected to Cloudflare as well as a bucket on S3 configured for static website hosting.

How I got it to work

  1. At this point, you should also have a bucket on S3 named after the custom domain you are using, something like www.mydomain.com. My bucket was setup for Static Website Hosting though that may not be a requirement.
  2. Your current registrar (GoDaddy, Namecheap, Route 53, etc.) should be pointing its DNS to Cloudflare’s name servers, something like beth.ns.cloudflare.com or buck.ns.cloudflare.com.
  3. In the Crypto tab on Cloudflare, set your SSL to Full (strict) and set Always Use HTTPS to On.
  4. On AWS, go to CloudFront and create a new distribution and choose Web.
  5. You can now select your Origin Domain Name. This will auto-populate with your relevant S3 bucket. Confusingly, I found that choosing the automatic URL didn’t work for me, so I pasted in what S3 provides as my public bucket URL, something like www.mydomain.com.s3-website.us.east.amazonaws.com (instead of www.mydomain.s3.amazonaws.com). Using the the shorter version suggested by CloudFront led to bad redirects and XML access errors. Entering the location-bound URL may adversely affect performance.
  6. For Alternate Domain Names (CNAMEs) enter the public domain name you will be using (www.mydomain.com). The rest of the settings here are not critical to this process. Where possible I chose HTTPS enforcement over HTTP.
  7. The last step for me was setting the CloudFront Domain Name, which should look something like a93ifn39lzdjfk.cloudfront.net, to the www CNAME record on Cloudflare.

That should be all you need for Full (strict) SSL access to your statically hosted S3 files on a custom Cloudflare domain.

Another approach (which I could not get to work)

I have a hunch that there’s a simpler, more standard and secure way to achieve a similar result. I would still like to get the following approach to work:

  1. Use a standard S3 bucket not set to publicly host files.
  2. Setup CORS and possibly a new CloudFront user with narrow permissions to access the files in that bucket.
  3. Provision a new certificate from ACM (AWS Certificate Manager) explicitly for my domain www.mydomain.com. This is a handy, free feature on AWS but it didn’t seem to matter in the previous approach.
  4. Use CloudFront’s default S3 handle (www.mydomain.s3.amazonaws.com) for the Origin Domain Name and then pass that to Cloudflare for the www CNAME record.

I tried some combination of those steps and couldn’t get it to work, though I can’t help but think that something close to that strategy would work.

Feedback welcome

This approach took a lot of trial and error. I’m pretty sure getting this to work desirably required all of the above settings, but some may be incorrect if not superfluous. I would love feedback on better ways to achieve this result. Please reach out with any builds or criticisms.