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):
{
"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).
- Copy your CloudFront Domain Name (e.g.,

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)
<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:SourceArncondition block, OR the Default Root Object in CloudFront is not set toindex.html. - The Fix: Verify your CloudFront distribution settings under General -> Default Root Object set to
index.html. Next, check S3 Bucket Policy and ensure theAWS:SourceArnmatches 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 usingCtrl + Shift + R(Windows) orCmd + Shift + R(Mac).
Error 3: 404 Not Found on Deep Sub-Pages
- The Error Log (Browser)
<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 namedaboutinstead ofabout.htmlorabout/index.html. - The Fix: Configure CloudFront Custom Error Responses: Map HTTP Status Code
404to Response Page Path/index.htmlwith HTTP Response Code200(essential for Single Page Applications like React, Vue, or Angular).




