When you are learning Cloud, DevOps, or Terraform, one of the most common interview questions is:
“Can you explain a real-world Terraform project you’ve built?”
In this blog, I’ll walk you through a production-style static website hosting architecture on AWS using Terraform, where:
- The website is secure
- The S3 bucket is private
- Content is delivered globally using CloudFront
- #HTTPS is enabled
- No custom domain or #Route53 is required (free-tier friendly)
Why This Project?
Most beginners host static websites by making the S3 bucket public.
That approach works, but it is not secure and not used in production.
In real environments:
- #S3 buckets are private
- Only #CloudFront can access them
- Users access content over HTTPS
- Infrastructure is created using IaC (Terraform)
That’s exactly what this project demonstrates.
Architecture Overview
High-level flow:-
- The user accesses the website using a CloudFront URL
- CloudFront receives the HTTPS request
- CloudFront fetches content securely from a private S3 bucket using Origin Access Control (OAC)
- Content is cached at global edge locations for fast delivery
🔐 The S3 bucket is not publicly accessible
📁 Project Structure
terraform-static-website/
├── main.tf
├── variables.tf
├── outputs.tf
└── website/
├── index.html
└── styles.cssThis clean structure makes the project easy to understand and interview-ready.
Terraform Implementation (Explained)
1️⃣ AWS Provider Configuration
provider "aws" {
region = var.aws_region
}
region = var.aws_region
}
This initializes the AWS provider and allows the region to be configurable using variables.
2️⃣ Creating a Private S3 Bucket
resource "aws_s3_bucket" "static_site_bucket" {
bucket = var.bucket_name
}
bucket = var.bucket_name
}
This bucket stores website files.
It is not configured as a public website, which improves security.
3️⃣ Blocking Public Access (Security Best Practice)
resource "aws_s3_bucket_public_access_block" "block" {
bucket = aws_s3_bucket.static_site_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
bucket = aws_s3_bucket.static_site_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
This ensures:
- No public access
- No accidental exposure
- Only CloudFront can access the bucket
4️⃣ Uploading Website Files using Terraform
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.static_site_bucket.id
key = "index.html"
source = "website/index.html"
content_type = "text/html"
}resource "aws_s3_object" "css" {
bucket = aws_s3_bucket.static_site_bucket.id
key = "styles.css"
source = "website/styles.css"
content_type = "text/css"
}
bucket = aws_s3_bucket.static_site_bucket.id
key = "index.html"
source = "website/index.html"
content_type = "text/html"
}
bucket = aws_s3_bucket.static_site_bucket.id
key = "styles.css"
source = "website/styles.css"
content_type = "text/css"
}
Terraform uploads static files automatically during deployment.
5️⃣ CloudFront Origin Access Control (OAC)
resource "aws_cloudfront_origin_access_control" "oac" {
name = "s3-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
name = "s3-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
OAC allows CloudFront to securely fetch objects from a private S3 bucket.
It is the modern and recommended approach over OAI.
6️⃣ CloudFront Distribution
resource "aws_cloudfront_distribution" "cdn" {
enabled = true
default_root_object = "index.html"
}
enabled = true
default_root_object = "index.html"
}
CloudFront provides:
- HTTPS by default
- Global CDN
- High availability
- Low latency
7️⃣ S3 Bucket Policy (Critical Security Step)
resource "aws_s3_bucket_policy" "allow_cloudfront" {
depends_on = [aws_cloudfront_distribution.cdn]
}
depends_on = [aws_cloudfront_distribution.cdn]
}
This policy:
- Allows only CloudFront to access S3 objects
- Prevents direct public access
- Uses
AWS:SourceArnfor strict security
📤 Terraform Output
output "cloudfront_url" {
value = aws_cloudfront_distribution.cdn.domain_name
}https://dxxxxxxxx.cloudfront.net
value = aws_cloudfront_distribution.cdn.domain_name
}
🚀 Deployment Steps
terraform init
terraform plan
terraform apply
terraform plan
terraform apply
CloudFront deployment may take 5–15 minutes due to global propagation.
🔗 Connect With Me
- LinkedIn: Click here
- GitHub : Click here
- Portfolio: Click here
