TerraformPilot

Cloud Computing

Set Up AWS CloudFront CDN with Terraform

Configure AWS CloudFront distribution with S3 origin, custom domain, SSL certificate, and cache policies using Terraform.

LLuca Berton1 min read

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

#
  1. Use Origin Access Control (OAC) instead of Origin Access Identity (OAI)
  2. Enable Brotli and Gzip compression for smaller payloads
  3. Set appropriate cache TTLs — longer for static assets, shorter for HTML
  4. Use PriceClass_100 for cost savings if your users are mainly in US/EU
  5. Add security headers via response headers policy
  6. Enable versioning on S3 for rollback capability
  7. 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.

#Terraform#AWS#Cloud Computing#DevOps#Infrastructure as Code

Share this article