"403 Forbidden" and "Access Denied" are the most common errors in Amazon S3, and also the least helpful. S3 deliberately does not tell callers why access was refused, so you have to work it out. The good news: there are only a handful of causes, and you can check them in a fixed order.
First, identify who is asking
Every S3 decision depends on who makes the request:
- An anonymous visitor (a browser hitting a public website or object URL).
- An IAM user or role in the same account (your app, your CLI).
- A principal in another account.
- An AWS service such as CloudFront, Athena or a load balancer writing logs.
Run aws sts get-caller-identity with the same credentials your app uses. Surprisingly often, the request is not coming from the identity you think it is.
The checklist
1. Block Public Access
If the request is anonymous, Block Public Access (BPA) wins over any bucket policy. It exists at two levels, account and bucket, and either can block you. With BPA on, S3 also refuses to save a public bucket policy at all. For a public website, turn off BPA on that one bucket only; for everything else, keep it on and use CloudFront with Origin Access Control instead.
2. The bucket policy
- Is there a policy granting the action to this principal (or
"*"for public reads)? - Does the
Resourcematch? Object actions needarn:aws:s3:::bucket/*;ListBucketneedsarn:aws:s3:::bucket. - Is there an explicit Deny? Common ones: deny unless
aws:SecureTransportis true, deny unless the request comes through a specific VPC endpoint, deny unless encrypted with a given KMS key. A Deny overrides every Allow, including the admin's.
3. The IAM policy of the caller
For principals in the same account, either the IAM policy or the bucket policy can grant access. Check the identity's policies for the exact action. Remember that s3:GetObject and s3:ListBucket are separate: without ListBucket, a missing object returns 403 instead of 404, which looks like a permission problem but means "this key does not exist".
4. Cross-account: both sides must allow
For a principal in another account, the bucket policy must allow that principal and that principal's own IAM policy must allow the action. One without the other is a 403.
5. KMS encryption
If objects are encrypted with SSE-KMS, reading them also requires kms:Decrypt on the key, and writing requires kms:GenerateDataKey. The key policy must allow it, and for cross-account access the key must be a customer managed key that the other account is allowed to use (the AWS managed aws/s3 key cannot be shared). The error still says Access Denied from S3, which hides the real cause. CloudTrail shows the failing KMS call.
6. Object ownership and ACLs
With the recommended setting Bucket owner enforced, ACLs are disabled and the bucket owner owns every object. In older buckets with ACLs enabled, objects uploaded by another account may be owned by that account, so the bucket owner cannot read them. Switching Object Ownership to Bucket owner enforced fixes this for most designs.
7. VPC endpoint policies
If the caller sits in a private subnet and reaches S3 through a gateway or interface VPC endpoint, the endpoint's own policy can block buckets that are not listed. This catches teams who add a restrictive endpoint policy and forget about third-party buckets (package mirrors, vendor data).
8. Service control policies and permission boundaries
In AWS Organizations, an SCP can deny S3 actions or whole Regions for the account. Permission boundaries on a role can also cap what it may do. These show as Access Denied even when the identity policy looks fine.
9. Presigned URLs
A presigned URL carries the permissions of whoever signed it, and only until it expires. 403 means the signer lacked permission, the URL expired, the clock of the signer was wrong, or the request changed something that was signed (method, headers, Region).
10. The S3 website endpoint specifics
The static website endpoint only serves public content, never authenticated requests, and returns HTML 403 pages. If you use CloudFront with OAC, the origin must be the bucket's REST endpoint, not the website endpoint.
Tools that tell you the real reason
- CloudTrail (and data events for S3) shows the denied call, the principal and sometimes the KMS step that failed.
- IAM Policy Simulator evaluates a principal's policies against an action and resource.
- IAM Access Analyzer flags buckets shared publicly or with other accounts.
- The error code in the XML body:
AccessDenied,AllAccessDisabled,InvalidObjectState(a Glacier object that needs restoring) and others point in different directions.
Practice on real broken buckets
Cloud Arena's break-fix labs start with a broken setup in a real AWS account and check your fix: Bakery Website Returns 403 Forbidden, Reports Bucket Returns Access Denied, Report Server Can't Read from S3 and Finance Reports Fail with KMS AccessDenied.
FAQ
Why do I get 403 instead of 404 for a missing file?
Without s3:ListBucket permission, S3 will not reveal whether a key exists, so it answers 403. Grant ListBucket on the bucket and missing keys return 404.
I am an admin, why am I denied?
An explicit Deny in the bucket policy, an SCP or a KMS key policy that does not include you applies to admins too.
Is turning off Block Public Access safe?
Only on a bucket that holds public website content, with a policy limited to s3:GetObject. For anything else, keep it on.