---
cuppy_hub: aws
cuppy_path: guides/s3/fix-403.md
cuppy_section: docs
cuppy_doc: YF6K-W93W
cuppy_doc_uuid: fda9b036-6e6c-4195-8824-97f0807958a3
cuppy_permalink: https://cuppy.io/aws/d/YF6K-W93W
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/guides/s3/fix-403
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 37bad7fa4202e6db6c38c67a56bc6205abb2f2b5b73792104da84c9f76a688bb
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Diagnose an S3 403 Forbidden

**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).


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 131 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/aws/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
