Static Website Hosting on AWS with Terraform
Implemented a secure and performant static website architecture using AWS services — including S3 for storage, CloudFront for content delivery, and ACM for SSL/TLS encryption. The entire infrastructure was provisioned using modular and reusable Terraform code, enabling consistent, scalable, and fully automated deployments.
Project Overview
This project demonstrates the deployment of a fully automated, secure, and highly available static website infrastructure on AWS using Terraform. The website is hosted on Amazon S3 with CloudFront as the CDN to ensure low latency and high performance across global regions. The domain is managed via Route 53, providing DNS resolution and seamless domain mapping.
- Terraform Infrastructure as Code (IaC): Modular and reusable codebase for consistent and repeatable deployments.
- Amazon S3: Stores static website files with private bucket to restrict access for enhanced security.
- CloudFront with OAC: Ensures secure HTTPS access and restricts direct S3 access using Origin Access Control.
- ACM: SSL/TLS certificates are provided via AWS Certificate Manager for encrypted traffic.
- Route 53: DNS management for the custom domain
saeedafzal.click
, routing traffic to CloudFront. - Best Practices: HTTPS enforcement, limited HTTP methods, and secure IAM and bucket policies.
This implementation demonstrates expertise in AWS services, Terraform automation, and building production-ready cloud solutions.
Technical Architecture
Technology Stack
- Terraform 1.12+
- AWS CLI
- Amazon S3 bucket
- Route 53 DNS
- CloudFront
- Amazon certificate manager
Project Specifications
- Duration: 3 weeks
- Team Size: 1 Engineer
- Lines of Code: 800+ (Terraform, HTML, CSS)
- Environments: 1 (Production)
- Infrastructure: AWS S3, CloudFront (OAC), ACM, Route 53
- Security: Private S3 bucket via OAC, HTTPS enforced
- Automation: Terraform Modules
- Availability: Global CDN with CloudFront
- Performance: 99.9% Uptime, Low Latency
- Cost Optimization: Efficient static hosting
- Domain: saeedafzal.click
Challenges & Solutions
Challenge: Secure Access to Private S3 Bucket via CloudFront
Solution: Integrated CloudFront Origin Access Control (OAC) to securely serve content from the private S3 bucket, removing the need for public bucket access and ensuring tight access policies with AWS best practices.
Challenge: SSL Certificate Provisioning and Domain Validation
Solution: Automated provisioning of ACM certificates using Terraform with DNS-based validation through Route 53, ensuring secure HTTPS access without manual intervention.
Challenge: Clean DNS Routing and Domain Management
Solution: Configured Route 53 hosted zone and A-record alias to point to the CloudFront distribution, ensuring seamless routing of custom domain traffic (e.g., saeedafzal.click
).
Challenge: Terraform Module Organization and Reusability
Solution: Designed modular Terraform code structure for S3, CloudFront, ACM, and Route 53 resources, enabling better separation of concerns, scalability, and reusability across future projects.
Challenge: Bucket Policy & IAM Conflicts with OAC
Solution: Resolved access issues by applying a minimal bucket policy with conditional CloudFront access using AWS:SourceArn
, ensuring secure content delivery and compliance with AWS policy standards.
Code Highlights
CloudFront Configuration
resource "aws_cloudfront_distribution" "static-website-distribution" {
enabled = true
default_root_object = "index.html"
aliases = [ "saeedafzal.click" ]
origin {
# origion_id must unique its is internal to used by cloudfront for cachesing and other purposes
# origin_id tell which caching statergy to on which origin
# we associate origin id to each origin source
origin_id = local.static-website-origin-id
domain_name = var.static-website-dns
origin_access_control_id = aws_cloudfront_origin_access_control.static-website-oac.id
s3_origin_config {
# Leave empty we are using OAC (origin access control) it is obsolet used with OAI (origin access identity)
origin_access_identity = ""
}
}
default_cache_behavior {
# allow only GET & HEAD methods from user
allowed_methods = [ "GET", "HEAD" ]
# cloudfront cache only GET and HEAD content, If someone requests the same file again, CloudFront serves it fast from cache.
cached_methods = [ "GET", "HEAD" ]
# If someone types http://example.com, they get redirected to https://example.com.
viewer_protocol_policy = "redirect-to-https"
# It tells CloudFront which backend (origin) this behavior is for.
# in the above origin section we associated local.static-website-origin-id to the static s3 resouce, there can be mulitple origins and each origion must have unique id
target_origin_id = local.static-website-origin-id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
# locations = [ "PK" ]
}
}
viewer_certificate {
cloudfront_default_certificate = false
acm_certificate_arn = var.domain_acm_certificate
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
Route53 Setup
resource "aws_route53_record" "cloudfront_alias" {
zone_id = data.aws_route53_zone.domain-hosted-zone.zone_id
name = "saeedafzal.click"
type = "A"
alias {
name = var.cloudfront-domain-name
zone_id = var.cloudfront-hosted-zone-id
evaluate_target_health = false
}
}
AWS Certificate Manager Configuration
# Create certificate
resource "aws_acm_certificate" "web-domain-certificate" {
domain_name = var.domain_name
validation_method = "DNS"
lifecycle{
create_before_destroy = true
}
}
# Add recoed in hosted zone to validate you own the domain
resource "aws_route53_record" "web-domain-record" {
zone_id = var.domain_id
for_each = {
for dvo in aws_acm_certificate.web-domain-certificate.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
name = each.value.name
type = each.value.type
records = [ each.value.record ]
ttl = 60
}
# Tells aws to validate the domain record
resource "aws_acm_certificate_validation" "web-domain-validation" {
certificate_arn = aws_acm_certificate.web-domain-certificate.arn
validation_record_fqdns = [ for record in aws_route53_record.web-domain-record : record.fqdn ]
}
Project Gallery

Key Takeaways
Technical Learnings
- Gained hands-on experience with Terraform modules for reusable infrastructure code
- Learned secure integration of AWS S3, CloudFront, and ACM for static site hosting
- Understood the importance of DNS management with Route 53 and HTTPS enforcement
- Applied best practices for bucket policies, origin access control, and certificate management
Business Impact
- Reduced deployment time through automated and consistent Terraform provisioning
- Delivered a high-availability and globally distributed static site with 99.9% uptime
- Enabled secure access and improved site performance via CloudFront CDN
- Created a cost-effective and scalable solution ideal for portfolio and future client projects