DEV Community

alok-38
alok-38

Posted on

Secure Static Website Hosting on AWS: CloudFront + Private S3 Bucket with Origin Access Control

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
Enter fullscreen mode Exit fullscreen mode

Final working URL (This will not work at the end of this tutorial):

https://d11v1nvbkt9lgm.cloudfront.net/
Enter fullscreen mode Exit fullscreen mode

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-alok

  • Uploaded index.html

  • Enabled Static Website Hosting

⚠️ Important detail (root of many issues later):

  • S3 static website hostingS3 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
Enter fullscreen mode Exit fullscreen mode

❌ Problems We Hit (In Order)

Problem 1: 403 AccessDenied from CloudFront

Symptoms

  • Browser showed XML error

  • curl showed:

HTTP/2 403
server: AmazonS3
x-cache: Error from cloudfront
Enter fullscreen mode Exit fullscreen mode

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.com

  • devops-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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

You added the CloudFront-generated policy that:

  • Allows cloudfront.amazonaws.com

  • Restricts access to your distribution ARN

  • Allows s3:GetObject

This finally allowed:

CloudFront → S3
Enter fullscreen mode Exit fullscreen mode

✅ Final Verification (Proof It Works)

Browser

  • Page loaded correctly

curl output

HTTP/2 200
x-cache: Miss from cloudfront
server: AmazonS3
Enter fullscreen mode Exit fullscreen mode

Content returned

<h1>Hello from S3 + CloudFront!</h1>
Enter fullscreen mode Exit fullscreen mode

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)