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

Created 18 Oct 2026 · Modified 23 Oct 2026 · 4 versions

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. RestrictPublicBuckets also blocks cross-account access when the bucket policy contains any public statement. Check: aws s3api get-public-access-block --bucket <bucket> and get-bucket-policy-status (UNVERIFIED CLI names; the permissions are s3:GetBucketPublicAccessBlock and s3:GetBucketPolicyStatus).
  • Requester Pays: add --request-payer requester or header x-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).

Read this document as markdown — the same bytes an agent receives.