Home » How to Host a Static Website on AWS S3 and Speed It Up Using CloudFront

How to Host a Static Website on AWS S3 and Speed It Up Using CloudFront

Static website using cloudfront

Introduction

Serving static assets such as HTML files, CSS stylesheets, JavaScript bundles, and media directly from single-region web servers introduces geographical latency, bandwidth bottlenecks, and unnecessary compute costs. Hosting static assets inside Amazon S3 combined with Amazon CloudFront (AWS’s global Content Delivery Network) provides a serverless, highly available, and secure infrastructure operating entirely on edge location caching.

In this step-by-step architectural guide, we will provision a secure Amazon S3 bucket, restrict public internet access using Origin Access Control (OAC), deploy an Amazon CloudFront distribution for edge caching, and audit HTTP cache header responses using browser developer tools.

What I Used

  • AWS S3 – To store and host the static website
  • AWS CloudFront – To cache and serve content globally
  • A simple HTML file – My “website”
  • Browser DevTools – To test caching behavior

Now, Let go to do something amazing !

Step 1: Provision Secure S3 Bucket & Upload Static Web Assets

Provision an isolated Amazon S3 bucket to act as the origin store for your web assets:

  • Navigate to Amazon S3 > Create Bucket.
  • Name your bucket (e.g., mystaticbucket15092025).
  • Ensure Block all public access remains CHECKED (ENABLED). CloudFront will access S3 privately via IAM controls, eliminating public internet exposure.
  • Upload your static web content files (index.html, style.css, app.js) directly into the root directory of your S3 bucket.

Step 2: Test your static website

  • Check your static website is working properly.
  • Go to bucket properties search for static website hosting block
  • Copy the URL from there and paste it your browser 

Step 3: Create CloudFront Distribution with Origin Access Control (OAC)

Configure a global CloudFront edge distribution to pull and cache objects securely from your private S3 bucket:

  • Navigate to AWS CloudFront > Create Distribution.
  • Distribution Name : StaticWebsite-CDN
  • Origin Domain: Select your S3 bucket REST endpoint (mystaticbucket15092025.s3.amazonaws.com).
  • Origin Access: Select Origin Access Control settings (recommended).
  • Click Create Control Setting and accept default settings.
  • Default Root Object: Type index.html.
  • Viewer Protocol Policy: Select Redirect HTTP to HTTPS to enforce encrypted SSL/TLS data transfer.
  • Click Create Distribution.

Step 4: Attach CloudFront OAC Bucket Policy to Amazon S3

  • Once the CloudFront distribution is created, CloudFront provides an automated bucket policy snippet to grant read access to your distribution identity.
    • In CloudFront, click Copy Policy from the orange banner warning.
    • Navigate back to Amazon S3 > Buckets > your-bucket > Permissions > Bucket Policy > Edit.
    • Paste the OAC bucket policy (ensuring only your CloudFront Service Principal can read objects):
Multi Copy Code Blocks
JSON

{
    "Version": "2012-10-17",
    "Statement": {
        "Sid": "AllowCloudFrontServicePrincipalReadOnly",
        "Effect": "Allow",
        "Principal": {
            "Service": "cloudfront.amazonaws.com"
        },
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::my-company-cdn-site-2026/*",
        "Condition": {
            "StringEquals": {
                "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1A2B3C4D5E6F7"
            }
        }
    }
}
    
  • Click Save Changes.

Step 5: Verify Edge Caching & Response Headers via DevTools

  • Wait 3 to 5 minutes for CloudFront edge locations to deploy global route updates.
    • Copy your CloudFront Domain Name (e.g., d111111abcdef8.cloudfront.net) and paste it into your browser.
    • Open Browser Developer Tools (F12) > Network Tab.
    • Reload the webpage and inspect the response headers for index.html:
    • First Request: X-Cache: Miss from cloudfront (Asset fetched directly from S3 origin).
    • Second Request: X-Cache: Hit from cloudfront (Asset served instantly from regional edge cache).

Step 6: Purge Edge Caches Upon Code Deployments

  • When updating your website’s static files in S3, edge servers continue serving cached versions until TTL expires. Force an instant global cache refresh:
    • Open CloudFront > Distributions > Invalidations.
    • Click Create Invalidation.
    • Add object path /* (purges all cached objects) and submit.

Enterprise Security & CDN Cost Best Practices:

  • Never Leave S3 Buckets Publicly Accessible: Using legacy public bucket policies exposes your raw S3 web assets to direct web scraping, hotlinking, and unauthorized downloads. Always enforce Origin Access Control (OAC) so users can only access assets through CloudFront’s SSL/TLS domain.
  • Cost Allocation for Cache Invalidations: CloudFront provides 1,000 free invalidation paths per month. After exceeding 1,000 paths, AWS charges $0.005 per path. For continuous deployments, append unique build hashes to filenames (e.g., app.v2.js) rather than running global /* invalidations repeatedly.

Production Troubleshooting: Common CloudFront & S3 Errors

Edge delivery issues can present generic browser error screens. Use the diagnostic matrix below to resolve delivery failures fast:

Error 1: 403 Access Denied / Forbidden Screen

  • The Error Log (Browser)
Multi Copy Code Blocks
PlainText

<Code>AccessDenied</Code>
<Message>Access Denied</Message>
    
  • The Root Cause: The S3 Bucket Policy is missing or contains an invalid CloudFront Distribution ARN inside the AWS:SourceArn condition block, OR the Default Root Object in CloudFront is not set to index.html.
  • The Fix: Verify your CloudFront distribution settings under General -> Default Root Object set to index.html. Next, check S3 Bucket Policy and ensure the AWS:SourceArn matches your exact CloudFront distribution ARN.

Error 2: Updates Uploaded to S3 Are Not Showing on Website

  • The Error Log: Browsers continue displaying outdated HTML text or old image assets despite uploading new files to S3.
  • The Root Cause: CloudFront edge locations serve unexpired cached copies based on default TTL rules (24 hours).
  • The Fix: Execute a manual invalidation via CloudFront Console -> Invalidations -> Create Invalidation with path /*. Additionally, hard-refresh your local browser using Ctrl + Shift + R (Windows) or Cmd + Shift + R (Mac).

Error 3: 404 Not Found on Deep Sub-Pages

  • The Error Log (Browser)
Multi Copy Code Blocks
PlainText

<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
    
  • The Root Cause: Navigating to nested paths (e.g., [https://domain.com/about](https://domain.com/about)) fails because CloudFront looks for an S3 object named about instead of about.html or about/index.html.
  • The Fix: Configure CloudFront Custom Error Responses: Map HTTP Status Code 404 to Response Page Path /index.html with HTTP Response Code 200 (essential for Single Page Applications like React, Vue, or Angular).

Leave a Reply

Your email address will not be published. Required fields are marked *