Static web hosting on AWS S3 giving me 403 permission denied
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A 403 Permission Denied response from an S3-hosted static site usually means the website request reached S3, but S3 refused to serve the object. The fix depends on how you are hosting the site, because the correct permissions for public website hosting are different from the permissions used with private buckets behind CloudFront.
Know Which S3 Endpoint You Are Using
This is the first thing to verify. S3 exposes two common access patterns:
- the static website endpoint, used for public website hosting
- the REST API endpoint, used for normal object access and often used behind CloudFront
If you enabled static website hosting, test the website endpoint shown in the bucket properties, not just the generic object URL. A common source of confusion is that the REST endpoint and the website endpoint behave differently for directory indexes, redirects, and permissions.
If you request https://bucket.s3.amazonaws.com/ for a site that was meant to be served from the website endpoint, you may see a 403 even though the website endpoint works.
Public Website Hosting Requires Public Read Access
For direct S3 website hosting, the objects must be publicly readable. That means two things must be true:
- Block Public Access settings cannot prevent the policy
- the bucket policy must allow
s3:GetObjecton the website files
A minimal public-read bucket policy looks like this:
You can apply it with the AWS CLI:
If Block Public Access is still enabled for bucket policies, S3 ignores the public-read policy and your site keeps returning 403.
Check the Actual Object Keys
S3 is an object store, not a traditional filesystem. If your website expects index.html at the bucket root but the file was uploaded under a different key, S3 cannot serve it as the index document.
Verify both the website configuration and the uploaded keys:
If the index document is set to index.html, make sure that exact object exists at the expected path. A missing index document can surface as a 403 from the website endpoint.
Private Bucket Behind CloudFront Is a Different Setup
If the bucket is supposed to stay private, direct S3 website hosting is not the right model. In that pattern, CloudFront should read from the S3 REST endpoint using an origin access control or an older origin access identity, and the bucket policy should allow only CloudFront rather than the public internet.
That means a 403 can also happen when people mix the two patterns:
- static website hosting enabled, but bucket kept private
- CloudFront configured, but user tests S3 directly
- bucket policy grants public read, but CloudFront origin expects private access
Pick one model and configure permissions accordingly.
A Fast Troubleshooting Sequence
Use a short checklist instead of changing many settings at once:
That sequence tells you whether public access is blocked, whether a bucket policy exists, whether website hosting is enabled, and what the website endpoint returns.
Common Pitfalls
The most common mistake is enabling static website hosting but leaving Block Public Access enabled, which makes the public bucket policy ineffective.
Another common mistake is using the wrong endpoint during testing. The S3 website endpoint and the object endpoint are not interchangeable for website behavior.
It is also easy to upload files under the wrong prefix or forget the configured index document name. S3 does not infer website structure the way a regular web server might.
Summary
- A 403 from S3 website hosting usually means S3 reached the bucket but denied access.
- Check whether you are using the website endpoint or the REST endpoint.
- For direct S3 website hosting, objects must be publicly readable and Block Public Access must allow that policy.
- Confirm that
index.htmland any other referenced files exist under the expected keys. - If the bucket should stay private, serve it through CloudFront with the correct private-origin policy instead of public S3 website hosting.

