Table of Contents
Introduction
CloudFront is AWS’s global content delivery network. It caches your content at edge locations worldwide, reducing latency for users regardless of their location. This guide covers setting up a complete CloudFront distribution with Terraform, including S3 origin, custom domain, SSL, and optimized cache policies.
S3 Bucket as Origin
resource "aws_s3_bucket" "website" {
bucket = "my-website-${var.environment}"
}
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_versioning" "website" {
bucket = aws_s3_bucket.website.id
versioning_configuration {
status = "Enabled"
}
}
Origin Access Control
resource "aws_cloudfront_origin_access_control" "main" {
name = "s3-oac"
description = "OAC for S3 website"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
data "aws_iam_policy_document" "s3_policy" {
statement {
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.website.arn}/*"]
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.main.arn]
}
}
}
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id
policy = data.aws_iam_policy_document.s3_policy.json
}
CloudFront Distribution
resource "aws_cloudfront_distribution" "main" {
origin {
domain_name = aws_s3_bucket.website.bucket_regional_domain_name
origin_id = "S3Origin"
origin_access_control_id = aws_cloudfront_origin_access_control.main.id
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
aliases = [var.domain_name, "www.${var.domain_name}"]
price_class = "PriceClass_100"
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
cache_policy_id = aws_cloudfront_cache_policy.optimized.id
origin_request_policy_id = aws_cloudfront_origin_request_policy.cors.id
response_headers_policy_id = aws_cloudfront_response_headers_policy.security.id
viewer_protocol_policy = "redirect-to-https"
compress = true
}
# Cache static assets longer
ordered_cache_behavior {
path_pattern = "/assets/*"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
cache_policy_id = aws_cloudfront_cache_policy.static_assets.id
viewer_protocol_policy = "redirect-to-https"
compress = true
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/index.html"
error_caching_min_ttl = 10
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.main.arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
tags = {
Environment = var.environment
ManagedBy = "terraform"
}
}
Cache Policies
resource "aws_cloudfront_cache_policy" "optimized" {
name = "optimized-caching"
default_ttl = 86400
max_ttl = 31536000
min_ttl = 0
parameters_in_cache_key_and_forwarded_to_origin {
cookies_config {
cookie_behavior = "none"
}
headers_config {
header_behavior = "none"
}
query_strings_config {
query_string_behavior = "none"
}
enable_accept_encoding_brotli = true
enable_accept_encoding_gzip = true
}
}
resource "aws_cloudfront_cache_policy" "static_assets" {
name = "static-assets"
default_ttl = 604800
max_ttl = 31536000
min_ttl = 604800
parameters_in_cache_key_and_forwarded_to_origin {
cookies_config {
cookie_behavior = "none"
}
headers_config {
header_behavior = "none"
}
query_strings_config {
query_string_behavior = "none"
}
enable_accept_encoding_brotli = true
enable_accept_encoding_gzip = true
}
}
Security Headers
resource "aws_cloudfront_response_headers_policy" "security" {
name = "security-headers"
security_headers_config {
strict_transport_security {
access_control_max_age_sec = 63072000
include_subdomains = true
preload = true
override = true
}
content_type_options {
override = true
}
frame_options {
frame_option = "DENY"
override = true
}
xss_protection {
mode_block = true
protection = true
override = true
}
referrer_policy {
referrer_policy = "strict-origin-when-cross-origin"
override = true
}
}
}
SSL Certificate
resource "aws_acm_certificate" "main" {
provider = aws.us_east_1 # CloudFront requires us-east-1
domain_name = var.domain_name
subject_alternative_names = ["*.${var.domain_name}"]
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
}
Best Practices
- Use Origin Access Control (OAC) instead of Origin Access Identity (OAI)
- Enable Brotli and Gzip compression for smaller payloads
- Set appropriate cache TTLs — longer for static assets, shorter for HTML
- Use PriceClass_100 for cost savings if your users are mainly in US/EU
- Add security headers via response headers policy
- Enable versioning on S3 for rollback capability
- Use CloudFront Functions for URL rewrites instead of Lambda@Edge for simple logic
Conclusion
CloudFront with Terraform provides a scalable, secure CDN setup that’s fully reproducible. By combining S3, OAC, custom cache policies, and security headers, you get a production-ready content delivery infrastructure that serves your users from the nearest edge location with optimal performance and security.

