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
Hands-On Courses
Learn by doing with interactive courses on CopyPasteLearn:
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.




