Amazon S3 access and request errors
Look up an S3 access or request error by its code or message — every 403 AccessDenied variant S3 emits, and the region, signature, clock and credential errors — each with ranked causes, the check and the fix. Bucket, object, upload and throttling errors are in reference/s3/bucket-and-object-errors.md.
markdownreferencev4VP6N-203Kstage: operatetrust: officialtrust: fieldservice: s3interface: anyS3 AccessDenied403 Forbiddenno identity-based policy allowsno resource-based policy allowsexplicit deny in a resource-based policyPermanentRedirectAuthorizationHeaderMalformedIllegalLocationConstraintExceptionSignatureDoesNotMatchRequestTimeTooSkewedInvalidAccessKeyIdExpiredToken S3HeadersNotSignedBlock Public AccessSSE-C blocked
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:
- 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.
- 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, thenget-object-aclfor the owner. Fix: set Bucket owner enforced (after migrating ACL grants), or re-copy the object. - KMS. SSE-KMS objects need
kms:Decryptto read andkms:GenerateDataKeyto write, allowed by the key policy and the caller's policy; an AWS managedaws/s3key works only for its own account. Check:aws s3api head-objectshows the key ID. - Object missing and no
s3:ListBucket. S3 answers 403 instead of 404. Check: grants3:ListBucketon the bucket ARN and retry; a 404 confirms it. - Requester Pays. Add
--request-payer requester(CLI) or thex-amz-request-payer: requesterheader. - Wrong identity or an expired/narrowed session. Check:
aws sts get-caller-identity. - Object Lock. Deleting a specific version under compliance retention or legal hold returns 403; governance retention needs
s3:BypassGovernanceRetention. - 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.