Key Takeaways
Is Your HealthTech Product Built for Success in Digital Health?
.avif)
Overview
This guide shows you how to combine AWS S3’s storage with Cloudflare’s global CDN network to create a simple, cost-effective solution.
I’m not a big fan of setting up CloudFront via Terraform, but I really like this simple and tested configuration for connecting S3 with Cloudflare and its proxy. It provides super-fast content loading, and with Cloudflare’s generous free tier, you can serve plain static content like avatars and public documents, as well as SPA applications deployed to S3.
Implementation
Firstly we create private S3:
locals {
cdn_bucket_name = "${local.name_prefix}-${local.env}-s3"
}
resource "aws_s3_bucket" "cdn" {
bucket = local.cdn_bucket_name
tags = local.tags
}
resource "aws_s3_bucket_public_access_block" "cdn" {
bucket = aws_s3_bucket.cdn.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_policy" "cdn" {
bucket = aws_s3_bucket.cdn.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = [
aws_s3_bucket.cdn.arn,
"${aws_s3_bucket.cdn.arn}/*",
]
# We want to restrict bucket to be accessed only from Cloudflare #
Condition = {
# Cloudflare IPs https://www.cloudflare.com/ips-v4/# #
IpAddress = {
"aws:SourceIp" = [
"173.245.48.0/20",
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"141.101.64.0/18",
"108.162.192.0/18",
"190.93.240.0/20",
"188.114.96.0/20",
"197.234.240.0/22",
"198.41.128.0/17",
"162.158.0.0/15",
"104.16.0.0/13",
"104.24.0.0/14",
"172.64.0.0/13",
"131.0.72.0/22",
]
}
}
},
]
})
depends_on = [aws_s3_bucket_public_access_block.cdn]
}
resource "aws_s3_bucket_website_configuration" "cdn" {
bucket = aws_s3_bucket.cdn.id
index_document {
suffix = "index.html"
}
error_document {
key = "index.html" # Using index.html for errors to support SPA routing
}
}
Then create domain like this:
locals {
cloudflare_zone_id = "123abcd456efg"
cdn_domain = "cdn.domain.com" // REMEMBER ! you can have only one domain nested from your root domain - some.cdn.domain.com WON'T WORK
}
resource "cloudflare_dns_record" "app_cname" {
zone_id = local.cloudflare_zone_id
name = local.cdn_domain
type = "CNAME"
ttl = 1
content = aws_s3_bucket_website_configuration.cdn.website_endpoint # must have s3-website.*.amazonaws.com
proxied = true
}
Here’s the cool part.
Cloudflare has introduced a special service called Cloudflare Connector that streamlines the process of setting up host headers, creating rules, and so on, consolidating everything under one service. Documentation is available here
Terrafrom resource:
resource "cloudflare_cloud_connector_rules" "app" {
zone_id = local.cloudflare_zone_id
rules = [
{
description = "cdn.domain.com"
enabled = true
expression = "(http.request.full_uri wildcard \"https://cdn.domain.com/*\")"
parameters = {
host = "some-cdn-s3.s3-website.<aws_region>.amazonaws.com" # must have s3-website.*.amazonaws.com
}
provider = "aws_s3"
}
]
}
…and that’s all. You can access your S3 by your domain through Cloudflare proxy.
Frequently Asked Questions

Let's Create the Future of Health Together
Looking for a partner who not only understands your challenges but anticipates your future needs? Get in touch, and let’s build something extraordinary in the world of digital health.