🌐Terraform Project: Hosting a Secure Static Website on AWS (S3 + CloudFront)

 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:-

Press enter or click to view image in full size



  1. The user accesses the website using a CloudFront URL
  2. CloudFront receives the HTTPS request
  3. CloudFront fetches content securely from a private S3 bucket using Origin Access Control (OAC)
  4. 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.css

This clean structure makes the project easy to understand and interview-ready.

Terraform Implementation (Explained)

1️⃣ AWS Provider Configuration

provider "aws" {
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
}

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
}

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"
}

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"
}

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"
}

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]
}

This policy:

  • Allows only CloudFront to access S3 objects
  • Prevents direct public access
  • Uses AWS:SourceArn for strict security

📤 Terraform Output

output "cloudfront_url" {
value = aws_cloudfront_distribution.cdn.domain_name
}
https://dxxxxxxxx.cloudfront.net

🚀 Deployment Steps

terraform init
terraform plan
terraform apply

CloudFront deployment may take 5–15 minutes due to global propagation.

🔗 Connect With Me