DEV Community

Ganesh Aravind Shetty
Ganesh Aravind Shetty

Posted on

Build a Secure Static Website on AWS S3 Using Terraform

Image description

Gitlab: https://github.com/GANESHARAVIND-124/AWS_S3_Bucket_Terraform.git

Deploying a static website on AWS S3 is easy, but making it secure, scalable, and highly available requires additional configurations. This guide will show you how to build a production-ready static website using Terraform while ensuring:

Security— Restrict direct S3 access, enforce HTTPS, and follow best IAM practices.
Performance— Use CloudFront CDN for fast, global content delivery.
Reliability— Integrate Route 53 for domain management and DNS resolution.

By the end of this tutorial, you will have a fully automated Terraform setup to launch a secure and optimized static website on AWS.

Introduction

In this blog, we will learn how to securely host a static website on AWS S3 using Terraform, while ensuring performance and security with CloudFront, Route 53, and IAM policies. This setup enables global content delivery, custom domain integration, and restricted S3 access via CloudFront Origin Access Control (OAC).

Why Use AWS S3 for Static Websites?

AWS S3 provides a highly available and scalable way to host static websites. However, to ensure security and performance, we integrate:

  • CloudFront (CDN) — Improves global performance and security.
  • Route 53 (DNS) — Configures a custom domain for easy access.
  • IAM Policies — Restricts direct access to S3, allowing only CloudFront.

Project Tech Stack

AWS S3 — Static website hosting
AWS CloudFront— Content Delivery Network (CDN)
AWS Route 53 — Domain Name System (DNS)
AWS IAM — Security policies

Project Structure

3-tier-webapp/
│
├── .terraform/ # Terraform’s internal files and state
│ ├── .terraform.lock.hcl # Lock file for provider versions
│ ├── terraform.tfstate # Current state of your infrastructure
│ └── modules/ # Cached modules
│
├── backend.tf # Backend configuration for state management
├── main.tf # Main configuration file for resources
├── providers.tf # Provider configuration (e.g., AWS)
├── terraform.tfvars # Variable values for the Terraform configuration
├── variables.tf # Variable definitions
│
├── modules/ # Custom modules for organizing resources
│ ├── compute/ # Module for compute resources (EC2, ALB, etc.)
│ │ ├── alb.tf # ALB configuration
│ │ ├── autoscaling.tf # Auto Scaling configuration
│ │ ├── ec2.tf # EC2 instance configuration
│ │ ├── outputs.tf # Outputs for the compute module
│ │ └── variables.tf # Variables for the compute module
│ │
│ ├── database/ # Module for database resources (RDS, etc.)
│ │ ├── rds.tf # RDS configuration
│ │ ├── outputs.tf # Outputs for the database module
│ │ └── variables.tf # Variables for the database module
│ │
│ └── networking/ # Module for networking resources (VPC, subnets, etc.)
│ ├── vpc.tf # VPC configuration
│ ├── subnets.tf # Subnet configuration
│ ├── nat.tf # NAT Gateway configuration
│ ├── security_groups.tf # Security Groups configuration
│ ├── outputs.tf # Outputs for the networking module
│ └── variables.tf # Variables for the networking module
│
└── README.md
Enter fullscreen mode Exit fullscreen mode

Step 1: Configure S3 for Static Website Hosting

Terraform Code (S3 Module)

resource "aws_s3_bucket" "static_site" {
  bucket = var.bucket_name
}

resource "aws_s3_bucket_website_configuration" "website" {
  bucket = aws_s3_bucket.static_site.id
  index_document {
    suffix = "index.html"
  }
}resource "aws_s3_bucket_policy" "allow_cloudfront" {
  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}/*"
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode

📌 Key Features:
✔ Enables static website hosting on S3
✔ Restricts public access & only allows CloudFront

🚀 Step 2: Secure Content Delivery with CloudFront

CloudFront will accelerate content delivery and enforce HTTPS for security.

Terraform Code (CloudFront Module)

resource "aws_cloudfront_distribution" "cdn" {
  origin {
    domain_name = aws_s3_bucket.static_site.bucket_regional_domain_name
    origin_access_control_id = aws_cloudfront_origin_access_control.s3_oac.id
  }

  enabled             = true
  default_root_object = "index.html"

  viewer_certificate {
    cloudfront_default_certificate = true
  }
}
Enter fullscreen mode Exit fullscreen mode

📌 Key Features:
✔ Uses Origin Access Control (OAC) — S3 is accessible only via CloudFront
✔ Enforces HTTPS — All traffic is securely encrypted

🌐 Step 3: Configure Route 53 for Custom Domain

If you have a custom domain, set up Route 53 to map it to CloudFront.

Terraform Code (Route 53 Module)

resource "aws_route53_record" "www" {
  zone_id = var.hosted_zone_id
  name    = var.domain_name
  type    = "A"
alias {
    name                   = aws_cloudfront_distribution.cdn.domain_name
    zone_id                = aws_cloudfront_distribution.cdn.hosted_zone_id
    evaluate_target_health = false
  }
}
Enter fullscreen mode Exit fullscreen mode

📌 Key Features:
✔ Maps a custom domain to the CloudFront distribution
✔ Improves website accessibility via user-friendly URLs

🔒 Step 4: Secure Access with IAM Policies

IAM policies help restrict access and follow least privilege principles.

Terraform Code (IAM Module)

resource "aws_iam_policy" "s3_readonly" {
  name        = "S3ReadOnlyPolicy"
  description = "Policy to allow read-only access to S3"

  policy = jsonencode({
    Statement = [{
      Effect   = "Allow"
      Action   = ["s3:GetObject"]
      Resource = "${aws_s3_bucket.static_site.arn}/*"
    }]
  })
}
Enter fullscreen mode Exit fullscreen mode

📌 Key Features:
✔ Prevents unauthorized access to S3 files
✔ Restricts permissions to only what’s necessary

📌 Deployment Guide

🔹 Step 1: Initialize Terraform

terraform init
Enter fullscreen mode Exit fullscreen mode

🔹 Step 2: Validate Configuration

terraform validate
Enter fullscreen mode Exit fullscreen mode

🔹 Step 3: Deploy Infrastructure

terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

🔹 Step 4: Verify the Website
Check the Route 53 DNS records:

aws route53 list-resource-record-sets --hosted-zone-id <HOSTED_ZONE_ID>
Enter fullscreen mode Exit fullscreen mode

Visit the website in your browser:
🔗 yourdomain.com

✅ Key Security Measures Implemented

🔒 CloudFront OAC — Restricts S3 access to CloudFront only.
🔒 HTTPS via CloudFront — Ensures encrypted traffic.
🔒 IAM Policies — Follows least privilege access control.

🚀 Future Enhancements

🔹 Automate deployments with CI/CD pipelines (GitHub Actions, AWS CodePipeline)
🔹 Add AWS WAF for DDoS protection & firewall rules
🔹 Implement logging & monitoring with AWS CloudWatch

🎯 Final Thoughts

This Terraform-based AWS static website hosting ensures:
Security— Only CloudFront can access S3
Performance— Global content delivery via CloudFront
Scalability— Easily extendable with automation

With this setup, you have a secure, scalable, and high-performing static website hosted on AWS! 🚀

🔥 Have questions or improvements? Let’s discuss in the comments!

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Billboard image

Create up to 10 Postgres Databases on Neon's free plan.

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Try Neon for Free →

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay