---
cuppy_hub: aws
cuppy_path: reference/s3/errors.md
cuppy_section: docs
cuppy_doc: VP6N-203K
cuppy_doc_uuid: 0867444d-2afc-472e-9042-ec352b22bbb4
cuppy_permalink: https://cuppy.io/aws/d/VP6N-203K
cuppy_version: 4
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/aws/docs/reference/s3/errors
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 2df3fc832c91a84e1429112a538db6380ee3fb2d164ea796ed23db72bb7d0f2e
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# Amazon S3 access and request errors

**S3 errors carry a machine code (`<Code>`), a human message, the resource, and a request ID.** Search by the code first. Two things change how you read them: a `HEAD` request has no response body, so you get only the HTTP status, never the code; and S3 gives a detailed access-denied message only when the caller and bucket owner are in the same account or organization. Outside that, it is a bare `Access Denied`.

## 403 `AccessDenied`

S3's detailed form:

```
User: <principal ARN> is not authorized to perform: <action> on resource: "<resource ARN>" <context>
```

| `<context>` ends with | Meaning | Go to | Check |
|---|---|---|---|
| `because no identity-based policy allows the <action> action` | caller's own policies lack an Allow | `reference/iam/errors.md` | `aws iam simulate-principal-policy --policy-source-arn <caller> --action-names <action> --resource-arns <arn>` |
| `because no resource-based policy allows the <action> action` | bucket or access point policy lacks an Allow **or** `IgnorePublicAcls` ignored the public ACL that would have allowed it | `reference/s3/access-control.md` | `aws s3api get-bucket-policy --bucket <b>`; `aws s3api get-public-access-block --bucket <b>` |
| `with an explicit deny in a resource-based policy` | a bucket/access point Deny matched **or** `RestrictPublicBuckets` blocked a public policy | `reference/s3/access-control.md` | `get-bucket-policy`: the `Deny` statements; `aws s3api get-bucket-policy-status --bucket <b>` |
| `because public ACLs are prevented by the BlockPublicAcls setting in S3 Block Public Access.` | the request tried to set a public ACL | Remove the ACL; ACLs are usually disabled anyway | `get-public-access-block` → `BlockPublicAcls` |
| `because public policies are prevented by the BlockPublicPolicy setting in S3 Block Public Access.` | `PutBucketPolicy` with a policy S3 classes as public | Make the policy non-public (fixed principals or keys) | `get-public-access-block` → `BlockPublicPolicy` |
| `because this bucket has blocked upload requests that specify Server Side Encryption with Customer provided keys (SSE-C). Please specify a different server-side encryption type` | SSE-C is blocked on the bucket (the default since April 2026) | Use SSE-S3 or SSE-KMS, or re-enable SSE-C (`reference/s3/encryption.md`) | `aws s3api get-bucket-encryption --bucket <b>` → `BlockedEncryptionTypes` |
| `… service control policy …`, `… resource control policy …`, `… VPC endpoint policy …`, `… permissions boundary …`, `… session policy …` | that policy type refused; explicit denies add `, with policy ARN: <arn>` | `reference/iam/errors.md` | The ARN in the message; `reference/organizations/scps-and-rcps.md` |

**When the message is just `Access Denied`** (cross-organization, VPC endpoint denials within an organization, directory buckets), work through these, most likely first:

1. **Cross-account without both sides allowing.** Caller's identity policy *and* bucket policy must both allow. Check: compare account IDs in the caller ARN and bucket owner.
2. **Object owned by another account.** With ACLs enabled (Bucket owner preferred / Object writer), a bucket-policy Allow only covers objects the bucket owner owns. Check: `aws s3api get-bucket-ownership-controls`, then `get-object-acl` for the owner. Fix: set Bucket owner enforced (after migrating ACL grants), or re-copy the object.
3. **KMS.** SSE-KMS objects need `kms:Decrypt` to read and `kms:GenerateDataKey` to write, allowed by the key policy and the caller's policy; an AWS managed `aws/s3` key works only for its own account. Check: `aws s3api head-object` shows the key ID.
4. **Object missing and no `s3:ListBucket`.** S3 answers 403 instead of 404. Check: grant `s3:ListBucket` on the bucket ARN and retry; a 404 confirms it.
5. **Requester Pays.** Add `--request-payer requester` (CLI) or the `x-amz-request-payer: requester` header.
6. **Wrong identity or an expired/narrowed session.** Check: `aws sts get-caller-identity`.
7. **Object Lock.** Deleting a specific version under compliance retention or legal hold returns 403; governance retention needs `s3:BypassGovernanceRetention`.
8. **Presigned URL.** It carries the signer's permissions and dies with the signer's credentials (`guides/s3/share-with-presigned-urls.md`).

## Request and signing errors

| Code (HTTP) | Message (S3's wording) | Likely cause → fix | Check |
|---|---|---|---|
| `AuthorizationHeaderMalformed` (400) | `The authorization header is malformed; the region '<r>' is wrong; expecting '<r2>'` | Signed for the wrong Region, or `endpoint` set alongside `region` (the SDK uses the endpoint). The global endpoint must be signed for `us-east-1`. Fix: set only `region` to the bucket's Region | `aws s3api head-bucket --bucket <b>` → `BucketRegion` vs the client's Region |
| `PermanentRedirect` (301) | The bucket you are attempting to access must be addressed using the specified endpoint… | Request sent to the wrong Region's endpoint. Fix: use the bucket's Region; SDK for JS v3 can follow it with `followRegionRedirects: true` at a latency cost | Same call |
| `IllegalLocationConstraintException` (400) | …You are trying to access a bucket from a different Region than where the bucket exists… | Same family: Region mismatch | `aws s3api get-bucket-location --bucket <b>` |
| `SignatureDoesNotMatch` (403) | The request signature that the server calculated does not match the signature that you provided. Check your AWS secret access key and signing method… | Wrong secret key; a proxy altering headers or query string; presigned URL used with a different method/headers than it was signed for; clock drift | `aws sts get-caller-identity` with the same key; compare the signed headers with those sent |
| `RequestTimeTooSkewed` (403) | The difference between the request time and the server's time is too large. | Fix the system clock (NTP) | `date -u` vs a time server |
| `InvalidAccessKeyId` (403) | The AWS access key ID that you provided does not exist in our records. | Deleted or wrong key: `reference/sts/credential-sources.md` | `aws sts get-caller-identity` fails the same way |
| `ExpiredToken` (400) | The provided token has expired. | Session or presigned URL's credentials expired: `reference/sts/errors.md` | The session's or presigned URL's `X-Amz-Date` + `X-Amz-Expires` |
| `AccessDenied` with `HeadersNotSigned: if-range` | — | Presigned URL signs `Range` but not `If-Range`; add `If-Range` to the signed headers | `X-Amz-SignedHeaders` in the URL lacks `if-range` |

## When none of these fits

Bucket, object, upload and throttling errors: `reference/s3/bucket-and-object-errors.md`. Collect the `RequestId` and the exact message, search the hub for the message, then follow `guides/s3/fix-403.md` for denials. AWS Support can trace a request from its request ID.


---
<!-- 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 -->
