AWS S3 Static Website Hosting with CloudFront CDN

Khimananda Oli 7 min read Database
AWS S3 Static Website Hosting with CloudFront CDN

By Khimananda Oli | Last reviewed: August 2026

AWS S3 static website hosting with CloudFront CDN is the standard architecture for serving high-performance, secure static content globally without managing servers. While S3 provides durable object storage, exposing it directly via the legacy website endpoint creates security risks and latency issues for international users. This guide details the modern production pattern: keeping your bucket private and using CloudFront Origin Access Control (OAC) to serve content securely.

Global UserHTTPS RequestCloudFrontEdge CacheWAF / HTTPSOAC AuthS3 BucketPrivate OriginLambda@Edge / Fn
Secure AWS S3 static website hosting with CloudFront CDN architecture using private origins and OAC authentication

How do you configure AWS S3 static website hosting with CloudFront CDN securely?

The most common mistake I see in audits is teams enabling the "Static Website Hosting" feature on the S3 bucket itself. In 2026, that legacy endpoint should remain disabled for production workloads. Instead, treat S3 purely as a private storage backend and let CloudFront handle all HTTP semantics, TLS termination, and access control. This approach aligns with zero-trust principles and simplifies compliance frameworks like SOC 2 and ISO 27001 because no public ingress exists on the storage layer.

Step 1: Create a private S3 bucket with versioning

Create your bucket with Block Public Access enabled. Enable versioning immediately; this is non-negotiable for rollback capability during failed deployments. If you are managing infrastructure programmatically, refer to my guide on infrastructure as code with Terraform to define these resources declaratively rather than clicking through the console.

resource "aws_s3_bucket" "site" {
  bucket = "khimananda-site-assets-prod"
}

resource "aws_s3_bucket_versioning" "site" {
  bucket = aws_s3_bucket.site.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_public_access_block" "site" {
  bucket                  = aws_s3_bucket.site.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Step 2: Configure CloudFront Origin Access Control (OAC)

OAC replaces the older Origin Access Identity (OAI) method. It uses IAM policy conditions tied specifically to the CloudFront distribution ID, preventing unauthorized services from accessing your bucket even if they guess the name. Attach an OAC resource to your distribution and update the S3 bucket policy to allow cloudfront.amazonaws.com as the principal.

resource "aws_cloudfront_origin_access_control" "oac" {
  name                              = "site-oac"
  origin_access_control_origin_type = "s3"
  signing_behavior                  = "always"
  signing_protocol                  = "sigv4"
}

# Bucket policy allowing only this specific CloudFront distro
data "aws_iam_policy_document" "bucket_policy" {
  statement {
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.site.arn}/*"]
    principals {
      type        = "Service"
      identifiers = ["cloudfront.amazonaws.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceArn"
      values   = [aws_cloudfront_distribution.site.arn]
    }
  }
}

Step 3: Set cache behaviors and SSL

Configure the default cache behavior to redirect HTTP to HTTPS. Set TTL values appropriate for your content strategy: immutable hashed assets can have a max TTL of one year, while HTML files should use shorter TTLs or rely on cache invalidations. Always assign an ACM certificate in us-east-1 for custom domains; CloudFront requires certificates in this region regardless of where your S3 bucket resides.

Why choose CloudFront over direct S3 website endpoints?

Direct S3 website endpoints lack critical production features. They support only HTTP (no native HTTPS), expose your bucket structure publicly, cannot integrate with WAF, and serve content from a single AWS region. For teams serving users across Nepal and globally, latency from a single-region S3 bucket becomes noticeable. CloudFront caches at edge locations worldwide, reducing time-to-first-byte significantly for distant users.

FeatureS3 Website EndpointCloudFront + Private S3
HTTPS SupportNo (requires external proxy)Native ACM integration
Access ControlPublic read requiredPrivate bucket + OAC
Global LatencySingle region origin300+ edge locations
DDoS ProtectionBasic Shield onlyWAF + Shield Advanced
Custom HeadersLimited metadataFull header manipulation
Cost ModelS3 requests + egressCloudFront requests + egress (often cheaper)

Beyond performance, the cost model often favors CloudFront for high-traffic sites. CloudFront’s first 1 TB of data transfer per month is free tier eligible, and egress rates from CloudFront are typically lower than direct S3 egress. When combined with reduced S3 GET requests due to caching, total spend frequently drops compared to direct hosting. For broader optimization strategies, review these cloud cost optimization tactics.

ViewerCloudFrontS3 OriginGET /index.htmlCache MISSOAC Signed Req200 OK + ObjectStore in Cache200 OK ResponseGET /style.cssCache HIT200 OK (Edge)
Request lifecycle in AWS S3 static website hosting with CloudFront CDN demonstrating cache miss origin fetch versus edge cache hit

How do you handle SPA routing and cache invalidation?

Single-page applications require special handling because routes like /dashboard don’t correspond to actual S3 objects. Without configuration, CloudFront returns 403/404 errors when users refresh or deep-link. You must implement two complementary patterns: custom error responses and intelligent cache invalidation.

Custom error responses for SPA fallback

Configure CloudFront to intercept 403 and 404 responses from S3 and return /index.html with a 200 status code instead. This allows client-side routers (React, Vue, Angular) to handle path resolution. Set the response page to /index.html, HTTP response code to 200, and cache TTL to 0 or a short duration to avoid stale index files.

Automated cache invalidation in CI/CD

When deploying new versions, invalidate cached paths so users receive updated content immediately. Wildcard invalidations (/*) are convenient but expensive; prefer specific paths or hash-based filenames to minimize invalidation costs. Integrate this into your deployment pipeline — if you use GitLab CI, see this CI/CD pipeline guide for patterns adaptable to static sites.

# Example: Invalidate only changed assets after deploy
aws cloudfront create-invalidation \
  --distribution-id E1ABC2DEF3GHIJ \
  --paths "/index.html" "/assets/manifest.json"

For frameworks generating hashed filenames (Vite, Next.js), invalidating only /index.html and manifest files suffices since all other assets are immutable. This reduces invalidation charges from hundreds of paths to just two or three per deployment.

What monitoring and security controls protect static hosting?

Static doesn’t mean unmonitored. Production deployments need observability for availability, performance, and security events. Enable CloudFront real-time logs delivered to Kinesis Data Streams or S3 for analysis. Standard access logs suffice for basic debugging but lack the granularity needed for incident response or compliance evidence collection.

  • WAF Integration: Attach AWS WAF web ACLs to block SQL injection, XSS, and rate-limit abusive IPs. Essential for any public-facing endpoint.
  • Geo Restrictions: Limit distribution to specific countries if licensing or compliance requires regional access controls.
  • Field-Level Encryption: Encrypt sensitive form data at the edge before forwarding to origins, useful for contact forms on static sites.
  • Origin Shield: Consolidate origin requests through a single regional endpoint to reduce S3 GET charges and improve cache hit ratios.
CloudFront DistEdge NetworkAWS WAFRate Limit / RulesReal-Time LogsKinesis / S3Shield AdvDDoS ProtectionPrivate S3OAC Protected
Defense-in-depth layers protecting AWS S3 static website hosting with CloudFront CDN including WAF, logging, and DDoS mitigation

Deploy production-ready AWS S3 static website hosting with CloudFront CDN

Implementing AWS S3 static website hosting with CloudFront CDN correctly requires disciplined configuration: private buckets, OAC authentication, HTTPS enforcement, SPA-aware error handling, and integrated security controls. Skip the legacy website endpoint entirely; the marginal convenience isn’t worth the security debt. Automate every resource definition through Terraform or CDK to ensure reproducibility and audit readiness. If your team needs help architecting compliant static hosting or migrating from legacy configurations, reach out to discuss your infrastructure requirements.

Frequently Asked Questions

Enable static hosting via the S3 console properties tab or AWS CLI put-bucket-website command. Specify index.html and error.html documents. Note that CloudFront distributions should use the REST API endpoint, not the website endpoint, for proper Origin Access Control integration.

CloudFront caches content at edge locations globally, reducing latency significantly compared to direct S3 access. It also provides free SSL certificates via ACM, DDoS protection through Shield Standard, and granular cache control headers that S3 website endpoints cannot offer natively.

Typically under one dollar monthly for small sites due to AWS Free Tier allowances. You pay only for requests and data transfer exceeding free limits. CloudFront includes 1TB free egress monthly, making it cheaper than direct S3 egress for most static workloads.

Implement Origin Access Control policies on the S3 bucket. This replaces legacy OAI identities and ensures only your specific CloudFront distribution can fetch objects. Remove all public read ACLs and bucket policies allowing anonymous access to enforce zero-trust security.

Yes. Request a free public certificate through AWS Certificate Manager in us-east-1. Attach it to your CloudFront distribution viewer certificate settings. Configure HTTP to HTTPS redirect behavior in the distribution to ensure all traffic uses encrypted connections exclusively.

CloudFront respects S3 Cache-Control headers. Without explicit headers, it caches for twenty-four hours. Set max-age directives in S3 object metadata during deployment to control TTL precisely. Use invalidation APIs sparingly since they incur per-path costs after free tier limits.

Absolutely. Add your domain as an alternate CNAME in CloudFront settings. Create CNAME records in Route53 pointing to the distribution domain name. Validate ownership via ACM DNS validation before attaching the certificate to avoid deployment failures during provisioning.

Verify Origin Access Control policy allows s3:GetObject for the distribution. Check S3 bucket encryption settings match OAC requirements. Ensure no conflicting bucket policies deny access. Test using curl with verbose output to distinguish between S3 authorization failures and CloudFront configuration issues.

No. Transfer Acceleration optimizes uploads to S3, not viewer downloads served through CloudFront. CloudFront already uses AWS backbone networks for origin fetches. Enabling both adds unnecessary cost without performance benefit for static website delivery scenarios where edge caching handles most requests.

Upload new files to S3 using AWS CLI sync or CI/CD pipelines. For immediate propagation, create a CloudFront invalidation for changed paths. Better yet, version filenames with hashes and update index.html references to leverage existing cache while serving fresh content instantly.

Enable compression for text assets. Set appropriate TTLs based on content type. Forward only necessary headers. Disable query string forwarding unless required. Use managed cache policies like CachingOptimized to reduce origin load and improve hit ratios across global edge locations.

Enable standard logging to S3 or real-time logs to Kinesis. Use CloudWatch metrics for cache hit ratio, bytes downloaded, and error rates. Set alarms on 4xx/5xx spikes. Analyze logs with Athena to identify uncached paths and optimize TTL configurations accordingly.

No. S3 only serves pre-built static files. Use Lambda@Edge or CloudFront Functions for lightweight edge logic like redirects. For full SSR, deploy on EC2, ECS, or Amplify Hosting instead. S3 plus CloudFront works exclusively for pre-rendered HTML, CSS, JavaScript, and media assets.

Configure custom error responses in CloudFront. Map 403 and 404 errors to return index.html with 200 status code. This allows client-side routers to handle navigation. Set response caching to disabled for these error pages to prevent stale route handling.

Use CloudFront Functions or response headers policies to inject Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options, and X-Frame-Options. These protect against XSS, clickjacking, and MIME sniffing attacks. Define them once in managed policies rather than modifying individual S3 object metadata repeatedly.