How to fix “Access Denied” (403) errors in S3
A 403 AccessDenied from Amazon S3 means the request was signed with real credentials but no policy allowed it, or something explicitly denied it. Work through the causes in this order: the wrong identity or profile; an IAM policy that puts s3:ListBucket on the object ARN instead of the bucket ARN, or s3:GetObject without the /* ARN; an explicit Deny in the bucket policy; Block Public Access; an SSE-KMS key you cannot decrypt; an object owned by another account; and an SCP, permission boundary or VPC endpoint policy. Two traps: S3 answers 403 instead of 404 for a missing object when you lack s3:ListBucket, and a wrong region or endpoint can surface as 403. S3 Viewer separates the two big cases for you: “The server rejected these credentials” means the key or secret is wrong, while “The server credentials don't allow this” means the key is valid and a policy said no.
Step-by-step.
- 01
Confirm which identity is making the call
A surprising share of 403s are the wrong profile, an expired SSO session, or an instance or container role you did not expect. Ask STS who you are before touching any policy. In S3 Viewer the identity is the access key saved on that server connection, so check which key you pasted.aws sts get-caller-identity # { # "UserId": "AIDA...", # "Account": "123456789012", # "Arn": "arn:aws:iam::123456789012:user/s3-viewer-readonly" # } - 02
Reproduce with the smallest possible call
Find out which action is denied. Bucket-level and object-level permissions are separate, andaws s3 lswith no bucket needs a third one,s3:ListAllMyBuckets, which is often the whole problem.aws s3api head-bucket --bucket my-bucket aws s3api list-objects-v2 --bucket my-bucket --prefix exports/ --max-keys 1 aws s3api get-object --bucket my-bucket --key exports/q3.pdf /tmp/q3.pdf - 03
Check the IAM policy's actions and ARNs
The most common cause.s3:ListBucketapplies to the bucket ARN (arn:aws:s3:::my-bucket), whiles3:GetObject,s3:PutObjectands3:DeleteObjectapply to the object ARN (arn:aws:s3:::my-bucket/*). Put an action on the wrong ARN and it silently grants nothing. Versioned buckets also needs3:GetObjectVersionto read old versions.{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetBucketLocation"], "Resource": "arn:aws:s3:::my-bucket" }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::my-bucket/*" } ] } - 04
Look for an explicit Deny in the bucket policy
An explicitDenyanywhere beats everyAllow. The usual suspects are conditions onaws:SourceIporaws:SourceVpcethat block your laptop or a server outside the VPC,aws:SecureTransportdenies hitting plain-HTTP clients, andaws:PrincipalOrgIDblocking a principal from another organization. For cross-account access, the caller's IAM policy and the bucket policy must both allow the action.aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text | jq . - 05
If the object is encrypted with SSE-KMS, check the key
Listing works but every download is 403? That is almost always KMS. Reading an object encrypted with a customer managed key needskms:Decrypton that key (uploads also needkms:GenerateDataKey), and the key policy itself must allow your principal. Cross-account KMS needs both sides, like a bucket policy.aws s3api head-object --bucket my-bucket --key exports/q3.pdf # "ServerSideEncryption": "aws:kms", # "SSEKMSKeyId": "arn:aws:kms:us-east-1:123456789012:key/..." - 06
Check who owns the object and whether public access is blocked
Objects uploaded by another account under the legacy ACL model can be unreadable even by the bucket owner. Fix it once by setting Object Ownership to Bucket owner enforced, which disables ACLs and makes the bucket owner own everything. Separately, Block Public Access (on by default since 2023) turns public-read policies and ACLs into 403s for anonymous requests; that is the feature working as intended. - 07
Check the guardrails outside S3
Service control policies, permission boundaries, session policies on an assumed role, and VPC endpoint policies can all deny a call that every S3-level policy allows. Requester Pays buckets return 403 unless you send--request-payer requester. Since 2021 the AccessDenied message usually names the policy type that denied you (“with an explicit deny in a service control policy”, “because no identity-based policy allows the s3:GetObject action”), so read the full message. CloudTrail records the same detail undererrorMessage. - 08
Rule out a 403 that is really a 404, or a wrong region
S3 deliberately answersGetObjecton a key that does not exist with 403 unless the caller hass3:ListBucket, so that unauthorized callers cannot probe for key names. If listing is not something you are meant to have, check the key spelling before the policies. A bucket in another region usually fails with 301 or 400, but some clients surface it as 403, so confirm the endpoint matches the bucket's location.aws s3api get-bucket-location --bucket my-bucket # { "LocationConstraint": "eu-west-1" } # null means us-east-1 - 09
In S3 Viewer, read which of the two messages you got
“The server rejected these credentials” maps toInvalidAccessKeyIdorSignatureDoesNotMatch: the key or secret is wrong, rotated, deleted, or pasted with a stray space. Fix the keys, not the policy. “The server credentials don't allow this” is a genuineAccessDenied: the key is valid and a policy said no, so work through steps 3 to 8. When connecting, if the key cannot list buckets S3 Viewer asks for bucket names and checks each withHeadBucket; a bucket that fails must be granted or removed from the list, and “Bucket name was not found” is a 404, so check the spelling and endpoint.
What's actually happening.
Every S3 request is authorized by evaluating all applicable policies together: the caller's identity-based policies, the bucket policy, object and bucket ACLs where they are still enabled, any session policy, plus the organization's service control policies, permission boundaries and the VPC endpoint policy if the call came through one. The rule is simple: an explicit Deny anywhere wins; otherwise at least one Allow must match the action and the resource ARN; and for a caller in another account, both that caller's policies and the bucket policy must allow it.
Resources are where most people slip. Bucket operations (ListBucket, GetBucketLocation) match arn:aws:s3:::my-bucket; object operations match arn:aws:s3:::my-bucket/*; and ListAllMyBuckets only matches *. Encryption adds a second authorization: reading an SSE-KMS object needs a KMS decrypt that succeeds under the key policy, and a KMS failure is reported by S3 as a plain 403.
S3 Viewer classifies the error codes it gets back from the bucket. Credential failures (InvalidAccessKeyId, SignatureDoesNotMatch, ExpiredToken) become “The server rejected these credentials”; AccessDenied becomes “The server credentials don't allow this”; a 404 on a bucket becomes “Bucket not found”. When a key cannot call ListBuckets, connecting falls back to HeadBucket on each bucket you name, so prefix- or bucket-scoped credentials work without any wildcard permissions.
Common questions.
What does “Access Denied” mean in Amazon S3?
Why do I get Access Denied when my IAM policy grants full S3 access?
Why does S3 return 403 instead of 404 for a file that doesn't exist?
Why can I list the bucket but not download objects?
Why do I get Access Denied on aws s3 ls?
Why does my public S3 bucket still return Access Denied?
How do I find out which policy is denying my S3 request?
What is the difference between AccessDenied, InvalidAccessKeyId and SignatureDoesNotMatch?
Why do I get Access Denied on Cloudflare R2?
Skip the CLI. Try it in the browser.
S3 Viewer turns the steps above into a single click. Open source, self-hostable, free.
Why teams pick this
More how-tos
Read-only IAM user
The exact least-privilege policy JSON, the console and CLI steps, and how to connect the keys without s3:ListAllMyBuckets.
Granular permissions
The IAM s3:prefix Condition that everyone misses, plus when workspace roles are simpler than IAM.
Download a file
Browser, AWS CLI, or presigned URL — three ways, with the object's own filename and zero key exposure.