Cloudflare/S3 Static Site Deployment

I’m a big fan of Ghost but I’m quickly becoming a fan of a static site generator called Hugo. For me, static site generators remove the overhead of maintaining the application powering the blog and allows me to focus on content.

I’ve actually been able to significantly decrease my hosting costs even further by leveraging Cloudflare’s CDN with an Amazon Web Services S3 bucket. This post will walk you through how I setup Cloudflare and AWS S3 to host this statically generated website.

AWS S3 Setup

The Amazon Web Services (AWS) setup is straightforward. I needed to:

  • Create an S3 bucket to host the static website;
  • Enable static website hosting on the S3 bucket;
  • Configure the S3 bucket policy to only allow connections from Cloudflare.
S3 Bucket creation

After logging into my AWS Console and navigating to the S3 console, I created a bucket.

For the bucket name, I used my website’s domain name (e.g. Since I like to live dangerously (like everyone else using AWS), I chose us-east-1 as my bucket’s region.


Next, I set a “domain” tag (as shown below). I use tags to track costs per project and website I host in AWS.


In S3, it is generally a bad idea to allow public access to your S3 bucket. However, my bucket will be hosting a public website so I need to allow public access. To do this, I unchecked all the block checkboxes and then checked the “I acknowledge that the current settings may result in this bucket and the objects within becoming public.” I use a bucket policy later to restrict access to the S3 bucket.


Enable Static Website Hosting

Once the bucket is created, I need to enable static website hosting under the bucket’s properties. This allows S3 to serve the bucket content. As shown below, I’ve enabled this feature and told S3 to use index.html as the index document and error.html as my error document. The endpoint generated by S3 is important as I need that later for Cloudflare.


S3 Bucket Policy

My personal preference is that visitors do not directly access the S3 bucket–direct access kind of defeats the purpose of having Cloudflare’s CDN cache my website. However, Cloudflare still needs access to the S3 bucket so public access cannot be fully disabled.

To allow Cloudflare but prevent everyone else from direct access, I’ve created a Bucket Policy to allow access based on IP addresses. This policy is populated with the list of Cloudflare IPv4 and IPv6 ranges.

The final bucket policy looks like:

    "Version": "2012-10-17",
    "Statement": [
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [

I added the policy by navigating to my bucket’s Permissions then Bucket Policy option and pasted the policy into the editor.


Cloudflare Setup

In my Cloudflare account, I added a CNAME record to my domain with the following properties:

  • Name: @
  • Target:
  • TTL: Auto
  • Proxy Status: Proxied

One last step that bit me was the Cloudflare SSL/TLS encryption mode. I had to set it to Flexible. Full is another encryption mode that I have used but I’ve only been successful when AWS CloudFront is used in front of the S3 bucket. CloudFront usage incurs an additional cost so I decided not to use it.

Once the DNS records have propagated, Cloudflare’s CDN is serving my website hosted in S3.

Deploying the Site

Once my S3 bucket was setup and Cloudflare was proxying traffic to it, I uploaded my statically generated content to the S3 bucket. I have since automated building my blog with Hugo and uploading it to the S3 bucket using a GitHub Action but I’ll cover that in a later post.


Hopefully this gave you some ideas for hosting your next website using Cloudflare, AWS S3, and Hugo. It is rather painless and so far, has been rock solid. Assuming us-east-1 doesn’t go down (again).