← Blog
A closed green ceramic door with a coral inset in a cream frame on a pale lavender background.
Published on
2026-11-02

S3 Access Denied: diagnose 403 errors on object reads

An S3 Access Denied response tells you that a request failed, but a 403 alone does not identify the cause. Start with the caller, operation, and exact object. Changing a bucket policy before checking those details can leave the original problem unresolved while granting unnecessary access.

This guide diagnoses object reads from Amazon S3 general purpose buckets. It covers signed CLI requests and points out where browser or presigned requests differ. Directory buckets, CloudFront origin configuration, and other S3-compatible providers need their own checks.

Capture the failed request before changing permissions

Record the time, AWS error code, request ID, operation, bucket, key, Region, and caller. Include a version ID if the application requested one. Keep authorization headers, session tokens, and complete presigned URLs out of shared tickets. Object keys can also contain sensitive names.

AWS’s 403 troubleshooting reference explains enhanced denial messages for requests within the same account or AWS organization. The message can identify a policy type and, for supported explicit denials, a policy ARN. It may report only one reason even when several apply. Requests across unrelated accounts, or denials from a VPC endpoint policy, can lack that detail. Treat a generic message as incomplete evidence.

1. Confirm the identity used by the failing client

Run the check through the same credential configuration as the failing workload. A successful request from your administrator profile does not establish that the application role can read the object.

The examples use an existing CLI profile named app-reader, a demonstration bucket, a small non-sensitive test object, and eu-west-1. Replace every placeholder, including the expected bucket owner’s account ID. That owner can differ from the caller in a cross-account setup.

aws sts get-caller-identity --profile app-reader --region eu-west-1

GetCallerIdentity returns the account and ARN associated with the credentials. Compare them with the intended role session. This command does not require permission to read S3, so success here is an identity check only. If the session is expired or the wrong role appears, correct the credential flow before editing storage permissions.

2. Check the exact key and interpret HEAD carefully

aws s3api head-object --bucket amzn-s3-demo-reports --key reports/sample.txt --expected-bucket-owner 123456789012 --profile app-reader --region eu-west-1

HeadObject retrieves metadata without downloading the body. Confirm the full key, capitalization, and any version selection against the application’s stored value. The --expected-bucket-owner check rejects a mismatched owner with 403; verify the account ID before treating that response as a policy problem.

A HEAD failure is generic and does not include the detailed error body of a GET response. More subtly, a nonexistent object can produce either 404 or 403: S3’s missing-object rules return 404 when the caller also has s3:ListBucket, and 403 without that permission. A 403 therefore does not prove that the key exists.

If the application already has permission to list this prefix, a small listing can help confirm the spelling:

aws s3api list-objects-v2 --bucket amzn-s3-demo-reports --prefix reports/ --max-keys 5 --no-paginate --expected-bucket-owner 123456789012 --profile app-reader --region eu-west-1

This ListObjectsV2 request retrieves at most five keys in one page. It is not a complete inventory, so an absent key in that page proves little. Listing requires s3:ListBucket on the bucket; reading an object is a separate permission. If listing is intentionally unavailable, ask an authorized owner to confirm the key rather than granting listing just to obtain a clearer error.

3. Separate metadata access from a real download

Use a known small test object and run the following in a new local directory. The command writes s3-read-check.txt and can overwrite an existing file with that name. AWS request, retrieval, or transfer charges may apply.

aws s3api get-object --bucket amzn-s3-demo-reports --key reports/sample.txt --expected-bucket-owner 123456789012 --profile app-reader --region eu-west-1 s3-read-check.txt

The GetObject command tests the actual read path. Compare the downloaded content with the expected fixture. Keep the profile, Region, bucket, and key unchanged between tests so that differences are interpretable.

ObservationWhat to check next
Listing works, download failsThe object read permission, exact key or version, and applicable encryption controls
HEAD works, GET failsThe GET error code, KMS authorization, and whether an archived object needs restoration
One role works, another failsThe intended role’s policies, session restrictions, and resource grants
The same role works only from one networkEndpoint policies and network conditions, while confirming the credentials are actually identical

For an SSE-KMS object, review the KMS permissions and key policy as well as S3 access. A bucket-policy Allow cannot grant access to the KMS key. A successful metadata request alone does not establish that the caller can decrypt a download.

Keep the initial HEAD test simple. The HeadObject API reference specifies additional KMS permissions when checksum mode is enabled. It also warns against sending upload encryption headers with HEAD for SSE-S3 or KMS-encrypted objects. SSE-C has a different customer-key header requirement and is outside these examples.

4. Trace the permission that applies to this request

For the current object, review s3:GetObject against the object ARN. A request for a specific version uses s3:GetObjectVersion. Do not confuse either with a bucket listing grant. Our bucket policy guide explains action, resource, and prefix scoping with a separate policy example.

Follow IAM’s policy evaluation rules: an applicable explicit Deny overrides an Allow. Review identity and resource policies alongside any permissions boundary, session policy, or organization control that applies. Adding another Allow does not remove a conflicting Deny.

For cross-account access, check the caller’s account and the resource owner’s grants together. Record the principal, action, resource, and condition being evaluated so the two administrators can investigate the same request.

If the network matters, inspect VPC endpoint controls. A bucket condition requiring a particular endpoint can reject a laptop or console request that takes another route. An endpoint policy can restrict requests too. Compare paths before weakening an intentional network boundary.

5. Check request-specific causes

  • Requester Pays: confirm whether the bucket requires billing acknowledgment. AWS documents --request-payer requester for downloads from Requester Pays buckets. Add it only when that billing arrangement is intended and approved; it is not a general 403 fix.
  • Presigned URL: compare the HTTP method, key, signed headers, and credential lifetime. A GET URL is not a HEAD URL. Do not change the method to inspect it casually. Our presigned URL guide covers signature and expiry checks.
  • Browser-only failure: inspect the failed request and its preflight. A CORS change does not grant object permission, and a CLI success does not validate the browser flow. Use our S3 CORS guide for that branch.

Make one narrow correction, then retest

Choose the correction supported by the evidence: the wrong session, a mistyped key, an incorrect expected owner, or a specific permission or condition. Preserve unrelated policy statements. Making the bucket public or granting s3:* is not a diagnostic step for a private reader.

Repeat the original request with the intended identity and network path. Check a known object outside the permitted scope where your access design requires a denial. Record the cause, reviewed change, and both outcomes. A working download demonstrates that request; it does not establish the privacy of every object.

The four command examples were checked locally against AWS SDK input models. They were not run against an AWS account, and no live IAM, KMS, network, or access outcome is claimed. Your final check is the actual application request after the reviewed correction.

Your next workload belongs on Hivenet.

Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.