When the AWS CLI prints An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied, your credentials worked. S3 authenticated the request, evaluated it against every applicable policy, and refused it.
$ aws s3 ls s3://amzn-s3-demo-bucket
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
That is a fundamentally different failure from Unable to locate credentials, which never reaches AWS at all. Here AWS knows exactly who you are; the question is what you are allowed to do.
Read the Full Error First
For requests made within the same AWS account — and for requests within the same AWS Organization — S3 returns enhanced access denied context that names the policy type responsible. The general shape is:
User: arn:aws:iam::123456789012:user/MaryMajor is not authorized to perform:
s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket/object-name"
because no identity-based policy allows the s3:GetObject action
or, when something actively denied you:
User: arn:aws:iam::777788889999:user/MaryMajor is not authorized to perform:
s3:GetObject on resource: "arn:aws:s3:::amzn-s3-demo-bucket/object-name"
with an explicit deny in a resource control policy
Two phrases carry the diagnosis:
with an explicit deny in a {type} policy— aDenystatement matched. An explicit deny always wins, and noAllowanywhere can override it.because no {type} policy allows the {action} action— an implicit deny. Nothing denied you; nothing granted you either, and IAM denies by default.
The {type} names where to look: identity-based policy, bucket policy, service control policy, resource control policy, or permissions boundary.
Cross-account requests from outside your organization return only a bare Access Denied with no context. Requests denied by a VPC endpoint policy also stay generic, as do requests to directory buckets.
Confirm Who You Actually Are
Before editing any policy, confirm which principal made the call:
aws sts get-caller-identity
A surprising share of these errors are simply the wrong profile — the console session is an administrator while the CLI is running as a limited CI user. If the ARN is not who you expected, switch profiles rather than changing permissions.
Work Through the Causes in Order
1. The action is not granted at all
s3 ls and s3 cp need different permissions, and this catches people constantly:
| Command | Permission | Resource ARN |
|---|---|---|
aws s3 ls s3://bucket | s3:ListBucket | arn:aws:s3:::bucket |
aws s3 cp s3://bucket/key . | s3:GetObject | arn:aws:s3:::bucket/* |
aws s3 cp file s3://bucket/key | s3:PutObject | arn:aws:s3:::bucket/* |
The bucket ARN and the object ARN are different resources. A policy granting s3:GetObject on arn:aws:s3:::bucket/* lets you download a known key while ls fails — exactly the confusing split people report.
Grant the specific action on the specific ARN:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
}
]
}
Resist "Action": "s3:*" with "Resource": "*". It makes the symptom disappear by granting standing access to every bucket in the account, which is a far larger problem than the one you started with.
2. The bucket policy does not allow your principal
For same-account access, either the identity policy or the bucket policy can grant permission. For cross-account access, both sides must allow it: the bucket policy must name your principal, and your identity policy must allow the action. One without the other fails.
3. An SCP or RCP denies it above your account
Service control policies and resource control policies set a ceiling that no account-level grant can exceed. If the error names one, the fix belongs in AWS Organizations, not in your IAM policy — and usually belongs to a different team.
4. Encryption is blocking the request
If the object is encrypted with SSE-KMS, S3 permissions alone are not enough. Reading needs kms:Decrypt; writing needs kms:GenerateDataKey. Both must be allowed by your IAM policy and by the KMS key policy. The failure surfaces as an S3 AccessDenied, which sends people hunting through bucket policies for a problem that lives in KMS. Our S3 encryption guide covers how the two interact.
5. Block Public Access, ownership, or Requester Pays
- Block Public Access overrides permissive bucket policies and ACLs for anonymous requests. Do not disable it to make an error go away — use a presigned URL or CloudFront with origin access control.
- Object Ownership / ACLs: an object uploaded by another account may not be owned by the bucket owner. Enforcing bucket owner ownership avoids the whole class of problem.
- Requester Pays buckets reject requests that do not include
--request-payer requester.
6. VPC endpoint policies
When traffic goes through a gateway or interface endpoint, the endpoint policy is evaluated too. This is the case where the enhanced message does not appear, so a stubbornly generic Access Denied from inside a VPC is a strong hint to check the endpoint policy.
Verify the Fix
Re-run the exact failing command, then confirm the grant is scoped as narrowly as you intended:
aws s3 ls s3://amzn-s3-demo-bucket
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/dev \
--action-names s3:ListBucket \
--resource-arns arn:aws:s3:::amzn-s3-demo-bucket
The policy simulator evaluates the request without performing it, so you can confirm a change works — and confirm neighbouring actions are still denied.
Prevent It
- Start from least privilege and add named actions as failures identify them. Starting broad and narrowing later almost never happens.
- Use IAM Access Analyzer to generate policies from CloudTrail activity rather than writing them from guesswork.
- Keep CloudTrail on. The
errorCodeanderrorMessagefields on a denied event give you the same diagnosis after the fact — see our CloudTrail logging guide. - Test with the simulator before deploying policy changes to shared environments.