About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.
Read the whole error first. For a request from the same account or the same organisation, Amazon S3 names the policy type and whether the denial was explicit or implicit, and often gives the policy ARN. From outside the organisation the message is a generic Access Denied, so both sides have to be checked by hand.
After that, confirm who is really calling, confirm the object exists, and walk the layers in the order AWS evaluates them. A KMS key is a separate authorisation with its own policy, so a perfect bucket policy does not settle it.
Commands below were written for this article from the synopses in the AWS CLI reference. None was run in a lab.
Why it matters
A 403 has many possible owners: a bucket policy, an IAM policy, a boundary, a session policy, an organisation policy, an endpoint policy, Block Public Access, an object owned by another account, or a KMS key. Adding s3:* to an identity policy cannot fix most of those, and it leaves a wider grant behind after the real cause is found.
Three traps are easy to miss:
- A 403 can mean the object does not exist and the caller lacks
s3:ListBucket. - A Block Public Access denial can read as a resource-based policy problem.
- An Allow in the bucket policy applies only to objects the bucket-owning account owns.
How evaluation works
AWS denies every request by default, apart from the account root user, and an explicit deny overrides any allow. An explicit denial means a Deny statement matched. An implicit denial means nothing denied the action and nothing allowed it.
The IAM documentation describes this sequence for a request within one account:
| Step | What is checked | Result that ends evaluation |
|---|---|---|
| 1 | Every applicable policy, looking for a matching Deny |
One explicit deny is final |
| 2 | Resource control policies (RCPs) | Explicit Deny in practice: RCPFullAWSAccess is attached at every level and can't be detached |
| 3 | Service control policies (SCPs) | No applicable Allow is a deny |
| 4 | Resource-based policy, such as the bucket policy | An Allow can be final, depending on the principal |
| 5 | Identity-based policies | No Allow is a deny |
| 6 | Permissions boundary, if set | No Allow is a deny |
| 7 | Session policy, if passed | No Allow is a deny |
Step 4 needs care. Within one account, a bucket policy naming an IAM user ARN or a role session ARN directly is not limited by an implicit deny in the identity policy, boundary or session policy. A bucket policy naming a role ARN is limited by an implicit deny in a boundary or session policy. The difference between a role ARN and a role session ARN can therefore change the outcome.
Same account against cross-account
Within one account, when the grant relies only on those two policies, one explicit Allow in either the bucket policy or the IAM user policy is enough, provided there is no explicit deny. A boundary, session policy or organisation policy can still refuse the request. Across accounts, AWS evaluates each account separately: the caller's account must allow the request and the resource's account must allow it.
Where SCPs and RCPs apply
The two organisation policy types point in opposite directions. SCPs apply to principals of the account where they are attached. RCPs apply to resources in the member accounts, and affect callers whether or not they belong to the organisation, including root users.
For an SCP, an Allow must exist at every level from the root through each organisational unit to the account. A deny at any one of those levels is enough to block. Effective permissions are the intersection of what these policies allow and what identity and resource policies allow, so adding an Allow to an SCP or RCP grants nothing by itself.
Read the error message
These forms come from the S3 403 troubleshooting guide.
| Message ends with | Meaning | Look at |
|---|---|---|
with an explicit deny in a service control policy or resource control policy, plus ARN |
A Deny in that organisation policy |
The named policy |
because no service control policy allows the s3:GetObject action |
SCP allow list has a gap | Every level above the account |
with an explicit deny in a VPC endpoint policy |
The endpoint policy denied | Endpoint in the request path |
because no VPC endpoint policy allows |
The endpoint policy has no matching Allow | Same |
because no permissions boundary allows |
The boundary excludes the action | Boundary on the user or role |
because no session policy allows |
The session was scoped down | Whatever issued the credentials |
with an explicit deny in an identity-based policy, in a permissions boundary or in a session policy |
A Deny in that policy |
The policy named, using the ARN if given |
because no identity-based policy allows |
No identity Allow | User, group and role policies |
because no resource-based policy allows |
No bucket policy Allow, or IgnorePublicAcls | Bucket policy and Block Public Access |
with an explicit deny in a resource-based policy |
A bucket policy Deny, or RestrictPublicBuckets |
Bucket policy and Block Public Access |
The message has limits that the guide states. Only one policy type is shown when several deny, only one reason is given when several apply, and the count of same-type policies is not given. Fixing the named cause can therefore reveal another 403 with a different message, which is progress.
Enhanced messages are not available for cross-account requests outside the organisation, and they are not returned when a VPC endpoint policy denies within the same organisation. The guide's examples contain no form that names a KMS key policy, so do not expect the text to point at one.
The check, in order
1. Who is calling
aws sts get-caller-identity
Run it in the same environment, with the same profile, as the failing request. An unsigned request is anonymous, and a presigned URL carries the permissions of whoever signed it, so test the signer's identity and not the person holding the link.
2. Whether the object exists
aws s3api head-object --bucket amzn-s3-demo-bucket1 --key path/to/object
Without s3:ListBucket, a missing object returns 403. With that permission on the bucket, it returns 404. Check the key from an identity that can list before changing any policy. A request for a specific version also needs s3:GetObjectVersion.
3. Bucket policy
aws s3api get-bucket-policy --bucket amzn-s3-demo-bucket1 --query Policy --output text
This needs s3:GetBucketPolicy. Read every Deny first, especially those with conditions on the source VPC, endpoint or encryption. The Allow must also match the resource form: arn:aws:s3:::bucket_name for bucket-level operations, and arn:aws:s3:::bucket_name/* or a prefix form for object-level operations.
4. Identity policy, boundary and session
The principal for an assumed role is the role session, not the role itself. Check the role's attached policies, its boundary, and how the session was issued, because a session can carry its own limiting policy. A boundary never adds permission; access is the intersection of it and the identity policy.
5. SCPs and RCPs
From the organisation's management account or a delegated administrator, for the account and for each parent above it:
aws organizations list-policies-for-target --target-id 111122223333 --filter SERVICE_CONTROL_POLICY
aws organizations list-policies-for-target --target-id 111122223333 --filter RESOURCE_CONTROL_POLICY
Use the principal's account for SCPs and the bucket's account for RCPs. An RCP denial is an explicit Deny in practice, because the default policy always supplies the Allow. RCPs do not affect resources in the management account or calls by service-linked roles, and they do not apply to AWS managed KMS keys. If FullAWSAccess has been removed and not replaced at any level, everything below it is blocked.
6. Network path
An endpoint policy does not override or replace identity or resource policies; it adds a further limit. The default policy on an S3 gateway endpoint allows full access, so a denial here means someone customised it.
Also check the bucket policy for conditions that demand a particular endpoint or VPC. An access point with a VPC network origin rejects requests from outside that VPC.
7. Block Public Access
aws s3api get-public-access-block --bucket amzn-s3-demo-bucket1
The four settings apply at access point, bucket and account level, and the most restrictive combination wins, so also check the account-level values. RestrictPublicBuckets limits a bucket or access point with a public policy to AWS service principals and authorised users in the owner's account, so cross-account and anonymous calls to it are blocked.
8. Object ownership and ACLs
aws s3api get-bucket-ownership-controls --bucket amzn-s3-demo-bucket1
aws s3api get-object-acl --bucket amzn-s3-demo-bucket1 --key path/to/object
Under Bucket owner enforced, ACLs are off and the bucket owner owns every object, so this step ends there. Under the other settings, an object written by another account belongs to that account. The guide states that access to it isn't controlled by the bucket policy. The bucket policy's Allow statements do not reach it, but the bucket owner can still explicitly deny it.
AWS gives these fixes: disable ACLs after moving any ACL-based permissions into a bucket policy, which is its recommendation; copy the object back; re-upload with bucket-owner-full-control; or leave the writer as owner and grant through an object ACL.
9. The KMS key
aws s3api head-object --bucket amzn-s3-demo-bucket1 --key path/to/object
aws s3api get-bucket-encryption --bucket amzn-s3-demo-bucket1
aws kms get-key-policy --policy-name default --key-id 1234abcd-12ab-34cd-56ef-1234567890ab --query Policy --output text
Run head-object from an identity that can read the object's metadata. Its ServerSideEncryption and SSEKMSKeyId fields show what that object actually uses, whereas the bucket setting is only the default for new writes. For a customer managed key, upload needs kms:GenerateDataKey, download needs kms:Decrypt, and multipart upload needs both.
KMS differs from S3 in one way that matters. Without permission from the key policy, IAM policies that allow access have no effect. The default key policy enables IAM policies, but a hand-written one may not. Key policies are Regional, and KMS also evaluates grants, SCPs and VPC endpoint policies.
Across accounts, neither the key policy nor the IAM policy alone is sufficient. Objects encrypted with the AWS managed aws/s3 key can't be shared cross-account. If no key is given at request time or in the bucket default, the uploading account's aws/s3 key is used. Objects written that way are then tied to a key that cannot be shared with other accounts.
One more case: policies that use the object ARN as encryption context stop working with an S3 Bucket Key, which uses the bucket ARN instead.
10. Less common causes
- Requester Pays: a request without the parameter returns 403. The CLI flag is
--request-payer requester. - Object Lock: a permanent delete of a protected version returns 403, and this is the protection working.
- SSE-C blocked: the message says so directly, and the setting is the bucket's
BlockedEncryptionTypes. - Access point delegation: cross-account use needs the bucket policy to delegate.
Getting evidence
CloudTrail
The enhanced message is also available in CloudTrail, in the errorCode and errorMessage fields. But GetObject, PutObject and HeadObject are data events, and trails do not log data events by default; additional charges apply. The lookup-events command covers management and Insights events from the last 90 days, so it will not show an object read.
For KMS, CloudTrail logs cryptographic operations such as GenerateDataKey and Decrypt, and in some scenarios failed calls. Cross-account KMS requests rejected for access denied are logged only in the caller's account, so look there and not in the key owner's account.
Policy simulator
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:user/ExampleUser \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::amzn-s3-demo-bucket1/path/to/object \
--resource-policy file://bucket-policy.json \
--resource-owner 444455556666
For a bucket in another account, --resource-owner must name that account. Without it, the owner of the resource and its policy defaults to the account of the simulated identity, so the simulator applies same-account rules and can report allowed for a request that fails. Leave the option out for a bucket in the caller's account. The same reference says a supplied resource policy is evaluated "for IAM users only", so this example deliberately uses an existing IAM user. It does not validate a role session; do not create new long-lived credentials simply to run it. EvalDecision returns allowed, explicitDeny or implicitDeny. It is a useful check, but its documented limits are serious for this problem:
- It does not support RCPs.
- Results can differ for VPC endpoint policies, role chaining, and multiple resource-based policies on a single resource.
- Except in the console, it does not fetch the bucket policy. You must supply it.
- It reports an SCP's result but does not show the matched statement.
Test the KMS action separately against the key ARN, and treat a simulator "allowed" as ruling out only the layers it evaluated.
What to choose and why
These are editorial recommendations.
Start from the message, and change nothing until the failing layer has a name. For a generic cross-account 403, ask the bucket owner to run steps 3, 6 and 7 from its side, because reading those settings needs permissions in the bucket's account.
Prefer Bucket owner enforced on any bucket where other accounts write. It removes step 8 from future incidents. For cross-account encrypted data, use a customer managed key, and have writers name it by fully qualified key ARN. An alias is resolved in the requester's account, so the data can end up encrypted with the writer's key rather than the bucket owner's.
Fix at the narrowest layer that is wrong, and retest with the same identity from the same network location. A test from an administrator's laptop proves little about a role reaching S3 through a VPC endpoint.
Turn on S3 data events for the buckets that matter before the incident, if the cost is acceptable. Events that were never logged cannot be produced later.
Risks and limits
Warning: A bucket policy can lock out everyone, including administrators. AWS warns that endpoint-restricting bucket policies can block all connections to the bucket. The documented recovery is to sign in as the root user and delete the policy, or for a member account, to launch a privileged session in IAM and delete it. Save a copy of the current policy before any edit, and confirm a recovery path first.
Warning: Do not attach a new or changed SCP or RCP at the organisation root without testing. AWS strongly recommends against untested RCPs at the root, and a missing SCP Allow at one level blocks everything beneath it.
Warning: Disabling ACLs removes any access granted through them. Move those permissions into a bucket policy first.
Do not loosen Block Public Access, add s3:*, or add kms:* to stop an error. Each widens access and may not touch the cause.
This article covers general purpose buckets. Directory buckets use a different authorisation path and do not return these enhanced messages. If every layer checks out, AWS advises retrieving the S3 request ID and contacting AWS Support.
No command here was run for this article. Check flags against your installed CLI version.
Sources
Opened 2026-11-10. Sources behind the corrections reopened 10 November 2026.
- Troubleshoot Access Denied (403 Forbidden) errors in Amazon S3
- Access policy language overview
- How Amazon S3 authorizes a request
- Controlling ownership of objects
- Using SSE-KMS
- S3 Bucket Keys
- Bucket policies for VPC endpoints
- Amazon S3 CloudTrail events
- GetObject and HeadObject
- IAM policy evaluation: deny and allow logic
- IAM cross-account evaluation
- Permissions boundaries
- Troubleshoot access denied in IAM
- IAM policy simulator
- Resource control policies
- SCP evaluation
- KMS key policies, default key policy, cross-account use, troubleshooting permissions, CloudTrail logging
- VPC endpoint policies and S3 gateway endpoints
- CloudTrail data events and record contents
- AWS CLI v2 reference: get-caller-identity, head-object, get-bucket-policy, get-public-access-block, get-bucket-ownership-controls, get-object-acl, get-bucket-encryption, get-key-policy, list-policies-for-target, simulate-principal-policy, lookup-events
This article was originally published on .