An error occurred (AccessDenied) when calling the GetObject operation
Fix S3 AccessDenied on GetObject: run aws sts get-caller-identity, then the IAM policy simulator. Covers bucket policy, Block Public Access and ARN mistakes.
An error occurred (AccessDenied) when calling the GetObject operation: Access Denied
Incident Overview
Fix S3 AccessDenied on GetObject: run aws sts get-caller-identity, then the IAM policy simulator. Covers bucket policy, Block Public Access and ARN mistakes.
medium fix·6 min read
By Vishvesh Patel·DevOps Engineer·Updated 21 September 2026
An error occurred (AccessDenied) when calling the GetObject operation: Access Denied An error occurred (AccessDenied) when calling the PutObject operation: User: arn:aws:sts::123456789012:assumed-role/app-role/i-0abc is not authorized to perform: s3:PutObject on resource: "arn:aws:s3:::acme-assets/logs/app.log" <Error><Code>AccessDenied</Code><Message>Access Denied</Message></Error>
Do this first3 steps
Run these in order. Each one tells you what its output means before you change anything.
• Confirm which identity is actually making the call
The Arn here is what must be allowed. On EC2 or ECS it is an assumed-role ARN, not the role ARN, and a policy written against the wrong one of those denies silently.
• Ask IAM whether that identity is allowed, instead of reading policies by eye
The simulator evaluates identity policies, and reports allowed or implicitDeny or explicitDeny. An explicitDeny means something is actively denying, which narrows it to an SCP, a permissions boundary, or a Deny statement.
• Check the bucket policy and Block Public Access separately
A bucket policy can deny even when IAM allows. Block Public Access also overrides any policy that would grant public access, and it is enabled by default on new buckets.
S3 denies by default, and four independent mechanisms can each block a request. An allow in one does not override a deny in another, so the question is always which one said no.
aws sts get-caller-identity
Start with who you actually are
Note the ARN is sts::...:assumed-role/app-role/..., not iam::...:role/app-role. A bucket policy Principal written as the assumed-role ARN with the session name will not match reliably. Use the role ARN in policies and the assumed-role ARN only when you know why.
aws sts get-caller-identity
Let IAM answer instead of reading policies
The simulator evaluates the real policy set:
This is far faster than reading four documents and simulating the logic in your head.
aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:role/app-role \ --action-names s3:GetObject \ --resource-arns arn:aws:s3:::acme-assets/logs/app.log
The resource ARN trap
The single most common IAM mistake with S3:
That grants nothing. Object actions need the object ARN with a key path; bucket actions need the bucket ARN. They are different resources:
s3:ListBucket on the bucket is needed for aws s3 ls and for aws s3 sync. Without it, sync fails in a way that looks like an object permission problem.
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::acme-assets"
}ListBucket also changes the error you get
If you have s3:GetObject but not s3:ListBucket, a request for a missing key returns AccessDenied rather than NoSuchKey. S3 does this deliberately so that a caller cannot probe which keys exist. So an AccessDenied on a path you believe should work may simply mean the object is not there.
Granting s3:ListBucket makes the real error appear.
Bucket policies
Look for "Effect": "Deny". A common one enforcing TLS:
That denies plain HTTP. An SDK configured with an HTTP endpoint hits it and reports a generic Access Denied.
Another frequent one requires a specific encryption header on upload:
Your PutObject then has to send that header.
aws s3api get-bucket-policy --bucket acme-assets --query Policy --output text | python3 -m json.tool
Block Public Access
All four settings default to true on buckets created since 2023. BPA overrides any policy or ACL that would grant public access, so a bucket policy with "Principal": "*" is simply ignored.
If you want public reads, CloudFront with Origin Access Control is the better pattern than disabling BPA: the bucket stays private and only CloudFront can read it.
aws s3api get-public-access-block --bucket acme-assets
KMS-encrypted objects
An object encrypted with a customer-managed key needs both S3 and KMS permission:
And the key policy must allow the principal too. A KMS key policy is not optional the way a bucket policy is: without a grant there, IAM permissions alone are not enough.
This is the cause when GetObject is denied on one bucket while working on another with identical IAM.
{
"Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "arn:aws:kms:eu-west-1:123456789012:key/1234abcd-..."
}Cross-account
Both sides must allow it. The bucket policy grants the other account's role, and that role's IAM policy must also permit the action.
Also set object ownership, or objects written by the other account stay owned by it and your own account cannot read them:
BucketOwnerEnforced disables ACLs entirely and makes the bucket owner own everything, which removes a whole class of confusing cross-account failures.
aws s3api put-bucket-ownership-controls --bucket acme-assets \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'A checklist
Frequently Asked Questions
Why does S3 return AccessDenied instead of NoSuchKey for a missing object?
Because without s3:ListBucket permission, telling you the object does not exist would leak information about the bucket's contents. S3 therefore returns AccessDenied for both a forbidden object and a missing one, so an unauthorised caller cannot probe which keys exist. Granting s3:ListBucket on the bucket ARN changes the behaviour and you start getting the real NoSuchKey error, which is worth doing in any environment where you are debugging paths.
What is the difference between a bucket policy and an IAM policy?
An IAM policy is attached to an identity and says what that identity may do. A bucket policy is attached to the bucket and says who may do what to it. Both are evaluated, and a request is allowed only if nothing denies it and at least one of them allows it. For same-account access an IAM policy alone is enough. For cross-account access both are required: the bucket policy must grant the external principal, and that principal's own IAM policy must also permit the action.
Why does my bucket policy granting public access not work?
Block Public Access is almost certainly enabled, and it overrides bucket policies and ACLs that would grant public access. All four of its settings default to on for buckets created since 2023. You can disable it, and a better pattern for serving public content is CloudFront with Origin Access Control: the bucket stays fully private, only the CloudFront distribution can read it, and you gain caching and TLS termination at the edge.
Why is GetObject denied on one bucket but not another with the same IAM policy?
The most likely difference is encryption with a customer-managed KMS key. Objects encrypted that way need kms:Decrypt in the caller's IAM policy and a grant for that principal in the KMS key's own key policy. Unlike a bucket policy, the key policy is not optional: IAM permissions alone cannot grant access to a KMS key that does not allow the principal. Check which key the bucket uses with aws s3api get-bucket-encryption.
How do I debug this without reading four policy documents?
Use aws iam simulate-principal-policy with the role ARN, the action and the exact resource ARN. It evaluates the real identity policies and returns allowed, implicitDeny or explicitDeny, which immediately tells you whether the problem is a missing grant or an active denial from an SCP, permissions boundary or Deny statement. If it returns allowed and the call still fails, you have narrowed the cause to the bucket policy, Block Public Access or KMS.
Reference and practice
Learn the underlying concept
Other AWS errors
Free, comprehensive DevOps tutorials and learning roadmaps. Master Docker, Kubernetes, CI/CD, and more.
Get notified about new tutorials and features.
• Roadmaps
• Interview Prep
• Certifications
• Company
© 2026 DevOpsLesson. All rights reserved.
Need to practice this scenario?
Simulate this error condition and practice recovery commands safely in our in-browser terminal.