Diagnose an S3 403 Forbidden
Eight checks, cheapest first, from an S3 403 to its cause — the message, the caller, missing-object-without-ListBucket, cross-account, object ownership, KMS, Block Public Access and organization/endpoint policies — and the smallest fix for each.
markdownguidev4YF6K-W93Wstage: operatetrust: officialservice: s3interface: cliinterface: sdkS3 403S3 Access Deniedtroubleshoot S3 access deniedbucket policyobject ownershipKMS decryptBlock Public AccessRequester Payspresigned URL 403
Read the message before changing any policy. S3 has more ways to say no than IAM alone: the bucket policy, who owns the object, the KMS key, Block Public Access, and organization or endpoint policies all vote. Work through these checks in order; each is cheap and rules out a whole class of cause.
1. Read the message
If the caller and the bucket owner are in the same account or organization, S3 names the refusing layer:
User: <arn> is not authorized to perform: s3:GetObject on resource: "arn:aws:s3:::<bucket>/<key>" because no resource-based policy allows the s3:GetObject action
Look up the ending in reference/s3/errors.md and go straight to that layer. A bare Access Denied means a cross-organization request, a VPC endpoint denial inside an organization, or a directory bucket: continue with step 2. Over HEAD (HeadObject, many SDK "exists" checks) you get only the status code, never a message: repeat the request as GetObject to see the message.
2. Confirm the caller
aws sts get-caller-identity
A presigned URL acts as whoever signed it, and stops working when that signer's credentials expire (often within hours for role credentials) even if the URL's own expiry is later. A wrong profile, instance role or narrowed session policy also shows up here.
3. Is the object actually there?
Without s3:ListBucket on the bucket, a missing object returns 403, not 404. Grant s3:ListBucket on arn:aws:s3:::<bucket> (or ask someone who has it to list the key). A 404 afterwards means the key is wrong: check case and URL-encoding.
4. Same account or cross-account?
Compare the account in the caller ARN with the bucket owner's. Reading the bucket policy (aws s3api get-bucket-policy --bucket <bucket>) needs s3:GetBucketPolicy.
- Same account: an Allow in the caller's IAM policy or the bucket policy is enough.
- Cross-account: both must allow. Fix the side that does not. If it is another account's bucket, hand the owner the exact action and resource.
Check both object and bucket ARNs: s3:GetObject/s3:PutObject need arn:aws:s3:::<bucket>/*; s3:ListBucket needs arn:aws:s3:::<bucket>.
5. Who owns the object?
aws s3api get-bucket-ownership-controls --bucket <bucket>
With Bucket owner enforced (the default) ownership is never the problem. With Bucket owner preferred or Object writer, an object uploaded by another account is owned by that account: the bucket policy's Allow does not reach it, only its ACL does. Fix, best first: switch to Bucket owner enforced after migrating ACL grants to the bucket policy; have the writer re-upload with bucket-owner-full-control; or copy the object over itself as the bucket owner.
6. Is it encrypted with KMS?
aws s3api head-object --bucket <bucket> --key <key>
If it shows aws:kms with a key ID, the caller needs kms:Decrypt (reads) or kms:GenerateDataKey (writes; multipart also needs kms:Decrypt) in the key policy and in its IAM policy. An AWS managed aws/s3 key works only for callers in the key's own account. If the bucket has a Bucket Key and the key policy conditions on the object ARN in the encryption context, it now sees the bucket ARN instead.
7. Block Public Access, Requester Pays, Object Lock
- Public access: anonymous or "anyone" access fails if any level (account, bucket, access point, organization) blocks it.
RestrictPublicBucketsalso blocks cross-account access when the bucket policy contains any public statement. Check:aws s3api get-public-access-block --bucket <bucket>andget-bucket-policy-status(UNVERIFIED CLI names; the permissions ares3:GetBucketPublicAccessBlockands3:GetBucketPolicyStatus). - Requester Pays: add
--request-payer requesteror headerx-amz-request-payer: requester. - Object Lock: deleting a specific version under retention or legal hold returns 403 by design. Retention modes and replication requirements:
reference/s3/replication-and-object-lock.md.
8. Organization and network layers
SCPs, RCPs and VPC endpoint policies can refuse S3 requests. A request through a VPC endpoint is also subject to bucket-policy conditions on aws:SourceVpce/aws:SourceVpc, and loses aws:SourceIp. See patterns/iam-to-vpc-endpoints.md and reference/organizations/scps-and-rcps.md.
Still denied
Collect the request ID and the exact message, run aws iam simulate-principal-policy for the caller with the bucket policy passed in, and follow guides/iam/fix-access-denied.md. If a bucket policy has locked out everyone, the account root user can delete it (in an Organizations member account, through a privileged root session).