Serving a Private S3 Bucket Securely with CloudFront and Origin Access Control
What I Built (End State)
A static website hosted on Amazon S3, served globally via Amazon CloudFront, using secure private access.
Browser / curl
↓
CloudFront Distribution (HTTPS)
↓
Origin Access Control (OAC)
↓
Private S3 bucket
↓
index.html
Final working URL (This will not work at the end of this tutorial):
https://d11v1nvbkt9lgm.cloudfront.net/
Result:
HTTP 200
Content served from S3
Cached by CloudFront
Bucket NOT public
🧱 Step-by-Step What I Did
1️⃣ Created an S3 bucket
Bucket name:
devops-bucket-alokUploaded
index.htmlEnabled Static Website Hosting
⚠️ Important detail (root of many issues later):
- S3 static website hosting ≠ S3 REST API endpoint
2️⃣ Created a CloudFront distribution
Origin → S3 bucket
Default behavior →
*Viewer policy → Redirect HTTP to HTTPS
Allowed methods → GET, HEAD
CloudFront domain created:
d11v1nvbkt9lgm.cloudfront.net
❌ Problems We Hit (In Order)
❌ Problem 1: 403 AccessDenied from CloudFront
Symptoms
Browser showed XML error
curlshowed:
HTTP/2 403
server: AmazonS3
x-cache: Error from cloudfront
Why it happened
CloudFront could reach S3
S3 refused the request
Bucket had no permission allowing CloudFront
➡️ This is NOT a networking issue
➡️ This is NOT HTTPS
➡️ This is IAM / authorization
❌ Problem 2: Confusion between S3 endpoints
AWS warned:
“This S3 bucket has static website hosting enabled. Use website endpoint.”
I was stuck choosing between:
devops-bucket-alok.s3.amazonaws.comdevops-bucket-alok.s3-website-us-east-1.amazonaws.com
Root cause
CloudFront OAC does NOT work with S3 website endpoints
Website endpoints require public access
OAC works only with S3 REST endpoint
Correct decision
✅ Use bucket REST endpoint
❌ Do NOT use website endpoint
❌ Problem 3: OAC created but still 403
I created:
Origin Access Control (devops-day0-oac)
Attached it to the origin
But still:
403 AccessDenied
Why
OAC ≠ magic
CloudFront signs requests
S3 still needs a bucket policy allowing those signed requests
❌ Problem 4: “Where do I check if it’s attached?”
This was the most frustrating part.
Missing step
CloudFront tells you explicitly:
“You must allow access using this policy statement”
But AWS does NOT auto-apply it.
✅ The Critical Fix (Breakthrough Moment)
3️⃣ Added S3 Bucket Policy (KEY STEP)
S3 → devops-bucket-alok → Permissions → Bucket policy
You added the CloudFront-generated policy that:
Allows
cloudfront.amazonaws.comRestricts access to your distribution ARN
Allows
s3:GetObject
This finally allowed:
CloudFront → S3
✅ Final Verification (Proof It Works)
Browser
- Page loaded correctly
curl output
HTTP/2 200
x-cache: Miss from cloudfront
server: AmazonS3
Content returned
<h1>Hello from S3 + CloudFront!</h1>
This confirmed:
CloudFront is fronting the request
S3 is the origin
Access control is correct
HTTPS works
🧠 What I Learned (Important)
Conceptual
Object storage vs server hosting
CDN vs origin
Why CloudFront exists
Why S3 should NOT be public
Why 403 ≠ networking issue
AWS-specific
S3 website endpoint vs REST endpoint*
OAC vs OAI (modern vs legacy)
Bucket policy is mandatory with OAC
CloudFront errors often originate from S3
DevOps reality
AWS UI hides critical steps
Error messages are technically correct but unhelpful
Troubleshooting is mostly permission tracing
Most time is lost due to missing one small policy
Top comments (0)