A production-ready Terraform configuration that provisions secure, scalable, and globally distributed static website infrastructure on AWS using S3, CloudFront, and Certificate Manager.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ User/Browser │───▶│ CloudFront │───▶│ S3 Bucket │
│ │ │ Distribution │ │ (Private) │
│ yourdomain.com │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ ▲
▼ │
┌──────────────────┐ │
│ ACM Certificate │ │
│ (SSL/HTTPS) │ │
└──────────────────┘ │
│
┌──────────────────┐ │
│ OAC │───────────┘
│ (Origin Access │
│ Control) │
└──────────────────┘
| Resource | Purpose | Key Features |
|---|---|---|
| S3 Bucket | Static file hosting | Private access, versioning, website configuration |
| CloudFront Distribution | Global CDN | HTTPS redirect, caching, compression, custom errors |
| Origin Access Control | Secure S3 access | Replaces legacy OAI, uses SigV4 signing |
| ACM Certificate | SSL/TLS encryption | Free SSL certificate with DNS validation |
| S3 Bucket Policy | Access control | Allows only CloudFront to access bucket content |
- Global CDN - CloudFront distribution for lightning-fast content delivery worldwide
- SSL/TLS Encryption - Automatic HTTPS with AWS Certificate Manager
- Security Best Practices - Origin Access Control (OAC), private S3 bucket, secure policies
- Cost-Optimized - Smart caching, compression, and PriceClass_100 for cost efficiency
- Version Control - S3 bucket versioning enabled for content rollback capabilities
- SPA-Ready - Custom error handling for Single Page Applications
- Resource Tagging - Comprehensive tagging strategy for cost allocation and management
- Infrastructure as Code - Fully reproducible and version-controlled infrastructure
aws-static-site/
├── main.tf # Main infrastructure configuration
├── variables.tf # Input variables definition
├── outputs.tf # Output values (URLs, IDs, etc.)
├── terraform.tfvars # Your variable values (not in git)
├── terraform.tfvars.example # Example variable values
├── LICENSE.txt # MIT license
└── README.md # Documentation
- Terraform >= 1.5.0
- AWS CLI configured with appropriate permissions
- A domain name (for SSL certificate and custom domain)
Your AWS user/role needs:
s3:*(for S3 bucket management)cloudfront:*(for CloudFront distribution)acm:*(for SSL certificate management)iam:GetRole,iam:PassRole(for service roles)
# 1. Clone the repository
git clone https://github.com/TanyaMushonga/terraform-s3_and_cloudfront.git
cd terraform-s3_and_cloudfront
# 2. Configure variables
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values
# 3. Initialize Terraform
terraform init
# 4. Preview changes
terraform plan
# 5. Deploy infrastructure
terraform apply# terraform.tfvars
region = "us-west-2"
bucket_name = "my-awesome-site-2024"
domain_name = "blog.yourdomain.com"
environment = "prod"
tags = {
Project = "My Awesome Blog"
ManagedBy = "Terraform"
Owner = "Your Name"
CostCenter = "Marketing"
}terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Default region for S3 and other resources
provider "aws" {
region = var.region
}
# us-east-1 provider for CloudFront certificate (required by AWS)
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}Why two providers? CloudFront SSL certificates must be created in us-east-1, while other resources can be in your preferred region.
resource "aws_s3_bucket" "static_site" {
bucket = var.bucket_name
tags = merge(var.tags, {
Name = var.bucket_name
Environment = var.environment
})
}
# Block all public access (CloudFront will access via OAC)
resource "aws_s3_bucket_public_access_block" "static_site" {
bucket = aws_s3_bucket.static_site.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}Security First: The bucket is completely private. Only CloudFront can access it through Origin Access Control.
resource "aws_cloudfront_origin_access_control" "static_site" {
name = "${var.bucket_name}-oac"
description = "OAC for ${var.domain_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}Why OAC over OAI? Origin Access Control is the modern, more secure replacement for Origin Access Identity, using SigV4 signing.
resource "aws_cloudfront_distribution" "static_site" {
origin {
domain_name = aws_s3_bucket.static_site.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.static_site.id
origin_id = "S3-${var.bucket_name}"
}
default_cache_behavior {
viewer_protocol_policy = "redirect-to-https" # Force HTTPS
compress = true # Enable compression
default_ttl = 3600 # 1 hour cache
cached_methods = ["GET", "HEAD"] # Cache read operations only
}
price_class = "PriceClass_100" # US, Canada, Europe (cost-optimized)
}Performance & Cost: Configured for optimal caching with geographic cost optimization.
resource "aws_s3_bucket_policy" "static_site" {
bucket = aws_s3_bucket.static_site.id
policy = jsonencode({
Statement = [{
Effect = "Allow"
Principal = { Service = "cloudfront.amazonaws.com" }
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.static_site.arn}/*"
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.static_site.arn
}
}
}]
})
}Zero Trust: Only your specific CloudFront distribution can access the bucket.
After running terraform apply, you'll need to:
- Configure DNS: Point your domain to the CloudFront distribution
- Validate Certificate: Add DNS validation records to your domain provider
- Upload Content: Deploy your static files to the S3 bucket
| Service | Usage | Est. Monthly Cost |
|---|---|---|
| S3 Standard Storage | 1GB | $0.023 |
| CloudFront | 10GB transfer | $0.85 |
| Route 53 | DNS queries | $0.40 |
| ACM Certificate | SSL cert | $0.00 |
| Total | ~$1.27/month |
- Private S3 Bucket - No public access, CloudFront-only access
- Origin Access Control (OAC) - Modern replacement for OAI
- HTTPS Enforced - All HTTP traffic redirected to HTTPS
- IAM Policies - Least-privilege access principles
- Resource Tagging - Complete resource attribution
- Infrastructure as Code - Version controlled, reproducible deployments
- Environment Separation - Support for dev/staging/prod environments
- Cost Optimization - Regional edge locations, smart caching
- Security by Default - Private resources, encrypted transit
- Monitoring Ready - CloudWatch integration, access logging support
- Portfolio Websites - Showcase your work globally
- Documentation Sites - Fast, searchable documentation
- Landing Pages - Marketing sites with global reach
- Static Blogs - Generated sites (Jekyll, Hugo, Gatsby)
- Single Page Applications - React, Vue, Angular apps
Infrastructure Code: terraform-s3_and_cloudfront - Complete Terraform configuration for AWS static site infrastructure
Live Example: aws-static-site-deploy - Actual website deployed using this infrastructure with CI/CD automation
🌐 Portfolio: tanyaradzwatmushonga.me
💼 LinkedIn: Tanyaradzwa T. Mushonga
📂 Infrastructure Repo: terraform-s3_and_cloudfront
🚀 Deployment Repo: aws-static-site-deploy
⭐ If this helped you:
- Star both repositories
- Follow me for more DevOps, AWS, and Terraform tutorials
- Connect on LinkedIn for cloud engineering insights
This infrastructure powers thousands of static sites worldwide with enterprise-grade security, performance, and reliability.