Secure Static Website Hosting on AWS: CloudFront + Private S3 Bucket with Origin Access Control
Source: Dev.to
End State
A static website hosted on Amazon S3, served globally via Amazon CloudFront with private access.
Browser / curl
↓
CloudFront Distribution (HTTPS)
↓
Origin Access Control (OAC)
↓
Private S3 bucket
↓
index.html
Final working URL (will not work until the tutorial is completed):
https://d11v1nvbkt9lgm.cloudfront.net/
Result after everything is set up:
- HTTP 200
- Content served from S3
- Cached by CloudFront
- Bucket not public
Step‑by‑Step What I Did
1️⃣ Create an S3 bucket
- Bucket name:
devops-bucket-alok - Uploaded
index.html - Enabled Static Website Hosting
Note: S3 static website hosting ≠ S3 REST API endpoint.
2️⃣ Create a CloudFront distribution
- Origin: the S3 bucket created above
- Default behavior:
* - Viewer policy: Redirect HTTP → HTTPS
- Allowed methods: GET, HEAD
CloudFront domain created: d11v1nvbkt9lgm.cloudfront.net
❌ Problem 1 – 403 AccessDenied from CloudFront
Symptoms
-
Browser displayed an XML error page.
-
curloutput:HTTP/2 403 server: AmazonS3 x-cache: Error from cloudfront -
CloudFront could reach S3, but S3 refused the request because the bucket had no permission for CloudFront.
Root cause
- CloudFront Origin Access Control (OAC) does not work with S3 website endpoints (those require public access).
- OAC works only with the S3 REST endpoint.
Correct decision – use the bucket’s REST endpoint.
Created an Origin Access Control named devops-day0-oac and attached it to the origin, but still received 403.
OAC is not magic; CloudFront signs requests, but S3 still needs a bucket policy that allows those signed requests.
❌ Problem 4 – “Where do I check if it’s attached?”
The most frustrating part was that CloudFront tells you:
“You must allow access using this policy statement”
AWS does not auto‑apply the required bucket policy.
3️⃣ Add S3 Bucket Policy (Key Step)
-
In the S3 console: devops-bucket-alok → Permissions → Bucket policy.
-
Paste the CloudFront‑generated policy that:
- Allows
cloudfront.amazonaws.com - Restricts access to your distribution ARN
- Grants
s3:GetObject
- Allows
After applying the policy, the request flow succeeded:
- CloudFront → S3
- Browser received HTTP/2 200,
x-cache: Miss from cloudfront
## Hello from S3 + CloudFront!
This confirmed that CloudFront is front‑ending the request, S3 is the origin, access control is correct, and HTTPS works.
What I Learned
Conceptual
- Difference between object storage and server hosting.
- CDN vs. origin responsibilities.
- Why CloudFront exists.
- Why S3 buckets should not be public.
- A 403 error is not a networking issue.
AWS‑specific
- S3 website endpoint vs. REST endpoint.
- OAC (modern) vs. OAI (legacy).
- A bucket policy is mandatory when using OAC.
- Many CloudFront errors actually originate from S3 permissions.
DevOps reality
- The AWS UI hides critical steps (e.g., attaching the bucket policy).
- Error messages are technically correct but often unhelpful.
- Troubleshooting is mostly about tracing permissions.
- Most time is lost due to a single missing policy statement.