->> Day-14 Host A Static Website in AWS S3 And CloudFront (using terraform)

Source: Dev.to | Jan 03, 2026 (UTC)

Introduction:

This mini project demonstrates how to deploy a static website on AWS using Terraform. We’ll create a complete static website hosting solution using S3 for storage and CloudFront for global content delivery.

The Problem with Public S3 Websites?

Hosting a static website directly from S3 by enabling public access has multiple issues:

  • Security risk: Anyone can access your bucket if misconfigured
  • Higher latency: Users far from the bucket region experience slower loads
  • No fine-grained access control
  • No proper HTTPS management

Production systems require private storage and controlled access, which is where CloudFront comes in.

Architecture

Instead of users accessing S3 directly, we introduce CloudFront as a secure middle layer.

Architecture Flow:

User → CloudFront (HTTPS) → Private S3 Bucket

Enter fullscreen mode

Exit fullscreen mode

What’s happening here?

Why Use CloudFront with S3?

Performance

CloudFront caches your static files at edge locations worldwide.
A user in India hits a nearby edge location instead of an S3 bucket in us-east-1.

Security

The S3 bucket is not public.
Only CloudFront can access it using Origin Access Control (OAC).

Cost Efficiency

Data transfer from S3 → CloudFront is cheaper than S3 → Internet.

Prerequisites

Before starting, make sure you have:

Step-by-Step Terraform Implementation

Step 1: Create a Private S3 Bucket
We start by creating an S3 bucket and explicitly blocking all public access.

# S3 bucket for static website hosting resource "aws_s3_bucket" "website" { bucket_prefix = var.bucket_prefix } # Make S3 bucket private 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 }

Enter fullscreen mode

Exit fullscreen mode

Step 2: Upload Static Files

Uploading files via Terraform requires care. Browsers rely on Content-Type headers to render files properly.

# Upload website files to S3 resource "aws_s3_object" "website_files" { for_each = fileset("${path.module}/www", "**/*") bucket = aws_s3_bucket.website.id key = each.value source = "${path.module}/www/${each.value}" etag = filemd5("${path.module}/www/${each.value}") content_type = lookup({ "html" = "text/html", "css" = "text/css", "js" = "application/javascript", "json" = "application/json", "png" = "image/png", "jpg" = "image/jpeg", "jpeg" = "image/jpeg", "gif" = "image/gif", "svg" = "image/svg+xml", "ico" = "image/x-icon", "txt" = "text/plain" }, split(".", each.value)[length(split(".", each.value)) - 1], "application/octet-stream") }

Enter fullscreen mode

Exit fullscreen mode

Using filemd5() ensures Terraform updates only changed files.

Step 3: Create Origin Access Control (OAC)

# Origin Access Control for CloudFront (Recommended over OAI) resource "aws_cloudfront_origin_access_control" "oac" { name = "oac-${var.bucket_prefix}" description = "OAC for static website" origin_access_control_origin_type = "s3" signing_behavior = "always" signing_protocol = "sigv4" }

Enter fullscreen mode

Exit fullscreen mode

Step 4: Configure the CloudFront Distribution

Now we configure CloudFront to:

# CloudFront Distribution resource "aws_cloudfront_distribution" "s3_distribution" { origin { domain_name = aws_s3_bucket.website.bucket_regional_domain_name origin_id = "S3-${aws_s3_bucket.website.id}" origin_access_control_id = aws_cloudfront_origin_access_control.oac.id } enabled = true is_ipv6_enabled = true default_root_object = "index.html" default_cache_behavior { allowed_methods = ["GET", "HEAD"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3-${aws_s3_bucket.website.id}" forwarded_values { query_string = false cookies { forward = "none" } } viewer_protocol_policy = "redirect-to-https" min_ttl = 0 default_ttl = 3600 max_ttl = 86400 } price_class = "PriceClass_100" restrictions { geo_restriction { restriction_type = "none" } } viewer_certificate { cloudfront_default_certificate = true } }

Enter fullscreen mode

Exit fullscreen mode

Common mistake: forgetting /* at the end of the bucket ARN.

>>Deployment

Run:

terraform init terraform plan terraform apply

Enter fullscreen mode

Exit fullscreen mode

Type yes when prompted to confirm deployment.

Then:

  1. Copy the CloudFront domain_name
  2. Open it in your browser
  3. Your site loads securely over HTTPS 🎉

Access Your Website
After deployment completes, Terraform will output the CloudFront URL:

website url = https://d2nrhnei8a1w1m.cloudfront.net

Enter fullscreen mode

Exit fullscreen mode

Cleanup
To destroy all resources and avoid charges:

terraform destroy

Enter fullscreen mode

Exit fullscreen mode

Type yes when prompted to confirm destruction.

Conclusion

Serving static websites directly from public S3 buckets might work for demos, but production systems demand security, performance, and control.

Using CloudFront with a private S3 bucket – provisioned via Terraform – gives you a scalable, secure, and professional setup that mirrors real-world architectures.

Happy building ☁️🚀

Reference

>> Connect With Me

If you enjoyed this post or want to follow my #30DaysOfAWSTerraformChallenge journey, feel free to connect with me here:

💼 LinkedIn: Amit Kushwaha

🐙 GitHub: Amit Kushwaha

📝 Hashnode / Amit Kushwaha

🐦 Twitter/X: Amit Kushwaha

For further actions, you may consider blocking this person and/or reporting abuse

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Читайте також