AWS IAM AccessDenied Errors: Step-by-Step Troubleshooting Guide
Most AWS IAM AccessDenied errors can be traced in under five minutes if you know what to extract from the message. This guide walks you through reading the error, understanding why it happened, and fixing it: fast triage checklist first, then every policy layer that can independently block a request.
Simple explanation
AWS checks every API call against a set of policies before allowing it through. If no policy grants permission, the request is denied. If any policy explicitly denies it, the request is blocked regardless of what other policies allow.
Two kinds of AccessDenied:
- Implicit deny: no Allow policy covers this action and resource. Nothing said “yes”. Fix: add an Allow.
- Explicit deny: a Deny statement directly blocked the action. Fix: find and remove the Deny, or restructure which principals it applies to.
Both look like AccessDenied or AccessDeniedException in the error. The distinction matters because an explicit deny wins over any Allow, regardless of where that Allow comes from.
Think of it like a building with keycards. Every door is locked by default. Your keycard has a list of rooms it can unlock (your identity policy). Red warning tape across a door means nobody gets through, even with a master key (explicit deny). Your manager may also have set a ceiling on which rooms your keycard can reach, regardless of what it says (permissions boundary). To enter a room, you need your keycard to allow it and nothing to block it.
When to use this page
Use this guide when:
- Your error message contains a principal ARN (
arn:aws:iam::...orarn:aws:sts::...). - The error text says “is not authorized to perform” or “AccessDeniedException”.
- You are debugging a failed
sts:AssumeRolecall. - You are troubleshooting permission boundaries, SCPs, or session policies.
This guide is for IAM authorization failures. If the error comes from SSH, OS-level permissions, or application-layer authentication, see Permission Denied Errors in AWS instead.
For S3-specific 403 errors that do not include a principal ARN, start with S3 Access Denied Errors. S3 has its own evaluation chain involving bucket policies and Block Public Access that this guide does not fully cover.
How AWS authorization works for AccessDenied
AWS evaluates a request through multiple policy layers in a fixed order. Understanding this order is what lets you debug systematically rather than guessing.
Default deny: every request starts as denied. AWS allows it only if a policy explicitly says Allow and nothing else says Deny.
EXPLICIT DENY WINS
A Deny in any evaluated policy overrides any Allow, anywhere. It does not matter if an identity policy, a resource policy, or another account says Allow. One Deny from an SCP, a permissions boundary, or a resource policy ends the evaluation. You cannot out-allow an explicit deny.
What gets evaluated:
| Layer | What it controls |
|---|---|
| Identity-based policy | What the user or role is allowed to do |
| Permissions boundary | Maximum permissions for the principal (acts as a ceiling) |
| Session policy | Further restriction applied when a role is assumed via STS |
| SCP (Service Control Policy) | Org-level guardrail applied to all principals in a target OU |
| Resource-based policy | Permission granted or denied from the resource side |
| VPC endpoint policy | Restricts what can flow through a specific VPC endpoint |
| Trust policy | Controls who is allowed to assume a role (not an action policy) |
For a request to succeed, the identity-based policy (or a resource-based policy for cross-account access) must Allow the action, and none of the other layers may Deny it.
Read IAM Policy Structure for the full evaluation logic and JSON format.
Fast triage checklist
START HERE
Most AccessDenied errors resolve at step 3, 4, or 8. Run this sequence before diving into any single policy document.
- Extract the principal, action, and resource from the error message.
- Identify the principal type: IAM user, assumed role session, service principal, or federated identity.
- Check identity-based policies: list all attached managed and inline policies on the role or user.
- Look for explicit denies: search every attached policy for
"Effect": "Deny"statements. - Check the permissions boundary: if one is attached, verify it allows the failing action.
- Check session policies: if the role was assumed via
sts:AssumeRolewith a policy parameter, check that policy. - Check resource-based policies: for S3, KMS, SQS, Lambda, SNS, and other services that support resource policies.
- Check SCPs: if the account is in an AWS Organization, an SCP may be blocking the action at the org level.
- Check the trust policy: only relevant if the error action is
sts:AssumeRoleitself, not a downstream action. - Re-test with the Policy Simulator: use
aws iam simulate-principal-policyto confirm the fix before deploying.
Reading the error message
IAM AccessDenied errors follow a consistent format. Parse these three fields every time:
An error occurred (AccessDeniedException) when calling the PutObject operation:
User: arn:aws:sts::111122223333:assumed-role/my-lambda-role/my-function
is not authorized to perform: s3:PutObject
on resource: "arn:aws:s3:::my-data-bucket/uploads/file.csv"
| Field | Value in this example | What to do |
|---|---|---|
| Principal ARN | arn:aws:sts::111122223333:assumed-role/my-lambda-role/my-function | Identify the identity type |
| Action | s3:PutObject | Search for this in policy Allow and Deny statements |
| Resource | arn:aws:s3:::my-data-bucket/uploads/file.csv | Check resource-based policies on this target |
Reading the principal ARN:
arn:aws:sts::...:assumed-role/ROLE-NAME/SESSION-NAME: an assumed role session. Check policies onROLE-NAME, not on the user or service that assumed it. The role name is the segment betweenassumed-role/and the final slash.arn:aws:iam::...:user/USER-NAME: a direct IAM user. Check policies attached to the user and their IAM groups.arn:aws:iam::...:role/ROLE-NAME: the role itself is the actor (rare in action errors; more common in service-to-service calls).- The denied action is
sts:AssumeRole: the denial is on the assumption itself, before credentials were issued. Check the target role’s trust policy.
ASSUMED ROLE
When you see assumed-role in the ARN, the policies to check are on the role, not on whoever assumed it. A Lambda function running as my-lambda-role needs that role to have the required permissions. Your IAM user’s permissions are irrelevant.
Common root causes of IAM AccessDenied
Missing allow in identity-based policy
The most common cause. The role or user has no policy that explicitly allows the action on the target resource. This produces an implicit deny.
aws iam list-attached-role-policies --role-name my-role
aws iam list-role-policies --role-name my-role # inline policies listed separatelyRun both commands. list-attached-role-policies only returns managed policies. Inline policies are stored separately and are easy to miss.
Explicit deny in identity-based policy
A Deny statement in one of the principal’s attached policies is directly blocking the action. This produces an explicit deny that cannot be overridden by any Allow.
Look for "Effect": "Deny" in every policy attached to the principal. The deny may be scoped to a specific action, a wildcard, or a condition key that matches the current request context.
Permissions boundary blocking access
A permissions boundary sets the maximum permissions a role can have. Even if the identity policy says Allow, the boundary must also cover the action. If it does not, the request is denied silently from the role policy’s perspective.
Boundaries are common in delegated-access environments where administrators allow developers to create roles but cap what those roles can do.
aws iam get-role --role-name my-role --query 'Role.PermissionsBoundary'If a boundary is attached, retrieve and review its policy document and verify it includes the failing action and resource pattern. IAM Policy Conditions explains how condition keys interact with boundaries when the boundary uses conditional allows.
Session policy blocking access
When code calls sts:AssumeRole and passes a Policy parameter, that inline session policy further restricts what the resulting credentials can do. The session can never exceed the role’s own permissions, and the session policy can narrow them further.
This is common in broker architectures, CI/CD pipelines, and multi-tenant systems where each session is scoped to a specific customer or resource set. See AWS STS and Temporary Credentials for how session policies are evaluated alongside role policies.
SCP blocking access
Service Control Policies (SCPs) from AWS Organizations apply to all principals in a member account, including the account root. If an SCP denies an action or does not include an Allow for it, no principal in that account can perform it, not even an administrator.
SCPs are not visible from inside the member account. To check them, you need access to the management account or a delegated admin account:
aws organizations list-policies-for-target \
--target-id ACCOUNT_ID \
--filter SERVICE_CONTROL_POLICYSCP BLIND SPOT
The IAM Policy Simulator does not evaluate SCPs. If the simulator says “allowed” but the live request fails, an SCP is the most likely cause.
Resource-based policy blocking access
Services like S3, KMS, SQS, Lambda, and SNS have resource-based policies evaluated alongside identity-based policies. A Deny in a resource policy blocks the request regardless of what the identity policy says.
For cross-account access, a resource-based policy must explicitly Allow the source account’s principal. The identity policy alone is not sufficient.
For S3 specifically, see S3 IAM vs Bucket Policies for how identity and bucket policies are evaluated together and where each one must allow the action.
VPC endpoint policy blocking access
If the request originates from a VPC using a gateway or interface endpoint, the request also passes through the endpoint’s policy. If the endpoint policy does not allow the action, the request is denied, even if the identity policy does allow it.
Check for VPC endpoint policies when:
- The service call originates from an EC2 instance, Lambda in a VPC, or an EKS pod.
- The failing service is S3, DynamoDB, or another service with a gateway endpoint in that VPC.
See VPC Endpoint Policies for how endpoint conditions restrict traffic.
Trust policy blocking sts:AssumeRole
If the denied action is sts:AssumeRole, the cause is in the trust policy of the target role, not in any identity policy on the calling principal. The trust policy must explicitly trust the source principal.
The source principal also needs an identity policy that allows sts:AssumeRole on the target role ARN. Both sides must be in place for cross-account assumptions. See Role Assumption in AWS for the full dual-side setup.
Service-specific authorization edge cases
Some services have additional authorization requirements beyond standard IAM:
- KMS: the key policy is required even for same-account access. If the KMS key policy does not grant the caller access, IAM alone cannot override it.
- Secrets Manager: resource-based policies on the secret can restrict access independently of IAM role policies.
- S3 Object Lock / Block Public Access: these produce 403 errors that look like IAM failures but are enforced at the bucket or object level.
- STS condition keys:
sts:AssumeRolecan be restricted with conditions likests:ExternalId,aws:SourceAccount, or OIDC claim values. A mismatch silently fails the assumption.
Using the IAM Policy Simulator correctly
The IAM Policy Simulator tests identity-based policies and permissions boundaries against a simulated request. It is useful for confirming that a role’s policies permit an action before deploying a fix, but it has real limits.
# Test whether a role can perform a specific action on a specific resource
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/my-lambda-role \
--action-names s3:PutObject \
--resource-arns arn:aws:s3:::my-data-bucket/uploads/file.csv# Test multiple actions at once
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/my-lambda-role \
--action-names s3:PutObject s3:GetObject s3:DeleteObject \
--resource-arns "arn:aws:s3:::my-data-bucket/*"The output shows "EvalDecision": "allowed" or "EvalDecision": "implicitDeny", plus which policy statement made the decision.
What the simulator evaluates:
- Identity-based policies attached to the principal (managed and inline)
- Permissions boundaries on the principal
What the simulator does not evaluate:
- SCPs from AWS Organizations
- VPC endpoint policies
- Resource-based policies on the target resource (for roles; there are limited same-account scenarios for users)
- Live policy propagation edge cases
SIMULATOR LIMITATION
A simulator result of “allowed” does not guarantee the live request will succeed. If the account is under an AWS Organization, always verify with a real API call after the simulator passes. An SCP may still block it.
For CLI-based IAM investigation beyond simulation, see Managing IAM with AWS CLI.
Step-by-step worked examples
Example 1: Lambda role cannot write to S3
Error (from CloudWatch Logs):
[ERROR] ClientError: An error occurred (AccessDeniedException) when calling the
PutObject operation: User: arn:aws:sts::111122223333:assumed-role/process-role/process-function
is not authorized to perform: s3:PutObject on resource:
"arn:aws:s3:::results-bucket/2026/03/19/output.json"Principal / action / resource: process-role / s3:PutObject / results-bucket
Inspect:
aws iam list-attached-role-policies --role-name process-role
aws iam list-role-policies --role-name process-role
aws iam get-policy-version \
--policy-arn arn:aws:iam::111122223333:policy/process-s3-policy \
--version-id v1 \
--query 'PolicyVersion.Document'The policy document shows:
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::results-bucket",
"arn:aws:s3:::results-bucket/*"
]
}Root cause: s3:PutObject is missing. The function was originally read-only and was later repurposed to write output files without updating the policy.
Fix: Add s3:PutObject to the Action list and create a new policy version:
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket", "s3:PutObject"],
"Resource": [
"arn:aws:s3:::results-bucket",
"arn:aws:s3:::results-bucket/*"
]
}aws iam create-policy-version \
--policy-arn arn:aws:iam::111122223333:policy/process-s3-policy \
--policy-document file://updated-policy.json \
--set-as-defaultVerify:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::111122223333:role/process-role \
--action-names s3:PutObject \
--resource-arns arn:aws:s3:::results-bucket/2026/03/19/output.jsonExpected: "EvalDecision": "allowed". Then trigger the Lambda to confirm the real request succeeds.
Example 2: Cross-account AssumeRole is denied
Error:
An error occurred (AccessDenied) when calling the AssumeRole operation:
User: arn:aws:iam::111122223333:user/deploy-bot
is not authorized to perform: sts:AssumeRole
on resource: arn:aws:iam::444455556666:role/deploy-rolePrincipal / action / resource: deploy-bot (account 111122223333) / sts:AssumeRole / deploy-role (account 444455556666)
Inspect both sides:
Side A: does deploy-bot have an identity policy that allows sts:AssumeRole on the target ARN?
aws iam list-attached-user-policies --user-name deploy-botThe policy must include:
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::444455556666:role/deploy-role"
}Side B: does the target role’s trust policy trust this specific principal?
# Run this in account 444455556666
aws iam get-role --role-name deploy-role \
--query 'Role.AssumeRolePolicyDocument'The trust policy must include:
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111122223333:user/deploy-bot"},
"Action": "sts:AssumeRole"
}Root cause: The trust policy listed arn:aws:iam::111122223333:root as the principal. This grants the account the ability to delegate the role, but deploy-bot still needs an explicit identity policy, which was missing.
Fix: Add the sts:AssumeRole Allow to deploy-bot’s identity policy, or update the trust policy to reference the user ARN directly. Both sides must allow the assumption.
See Role Assumption in AWS for the complete trust policy and identity policy setup.
Example 3: EKS pod / IRSA role gets AccessDenied
Error (from pod logs):
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the
GetSecretValue operation: User:
arn:aws:sts::111122223333:assumed-role/my-app-irsa-role/botocore-session-1234567890
is not authorized to perform: secretsmanager:GetSecretValue
on resource: arn:aws:secretsmanager:us-east-1:111122223333:secret:my-app-secretPrincipal / action / resource: my-app-irsa-role / secretsmanager:GetSecretValue / my-app-secret
Inspect:
First, confirm the pod is actually using the IRSA role and not falling back to the node’s instance profile:
kubectl get serviceaccount my-app-sa -n my-namespace -o yaml
# Look for: eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/my-app-irsa-roleCheck the IRSA role’s trust policy. It must trust the cluster’s OIDC provider and match the exact namespace and service account name:
aws iam get-role --role-name my-app-irsa-role \
--query 'Role.AssumeRolePolicyDocument'The condition block in the trust policy must match:
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLE123:sub":
"system:serviceaccount:my-namespace:my-app-sa"
}
}Then check the role’s identity policies for secretsmanager:GetSecretValue.
Root cause: The trust policy condition had a typo: my-app instead of my-app-sa in the service account subject claim. The pod could not assume the IRSA role and fell back to the node’s instance profile, which had no Secrets Manager access.
Fix: Update the trust policy condition to use the exact service account name. Restart the pod to pick up fresh credentials via the corrected OIDC flow.
See IAM Roles for Service Accounts (IRSA) for the full IRSA setup, OIDC provider configuration, and trust policy format.
IAM AccessDenied vs S3 Access Denied
S3 403 errors and IAM AccessDenied errors look similar but come from different evaluation chains. Mixing them up wastes debugging time.
IAM AccessDenied: the IAM evaluation engine blocked the request before S3 processed it. The error message includes the principal ARN and the denied action name.
S3 403 Access Denied: S3 evaluated its own authorization chain and returned a 403. The response body says “Access Denied” but typically does not include the principal ARN. S3 can return 403 for reasons that have nothing to do with IAM role policies:
- A bucket policy with an explicit Deny (or no Allow for cross-account access)
- Block Public Access settings blocking a public GET request
- An S3 Object Lock rule preventing deletion or overwrite of a locked object
- A VPC endpoint policy that does not allow the S3 action for that endpoint
For same-account access, IAM and the bucket policy are evaluated together. Either one allowing the action is sufficient, unless there is an explicit deny somewhere. For cross-account access, both the IAM policy and the bucket policy must explicitly Allow the request.
If you cannot find the cause in IAM policies and the service is S3, switch to S3 Access Denied Errors. For the complete breakdown of how IAM and bucket policies interact, see S3 IAM vs Bucket Policies.
Common mistakes
- Checking the user’s policies instead of the role’s policies. When the error ARN contains
assumed-role, the permissions live on the role. The IAM user who assumed the role is irrelevant. - Forgetting inline policies.
list-attached-role-policiesonly returns managed policies. Runlist-role-policiesseparately to find inline policies, which are stored and evaluated differently. - Ignoring permissions boundaries. A boundary silently caps what the role can do even if the identity policy says Allow. Check for one with
aws iam get-role —query ‘Role.PermissionsBoundary’before concluding the identity policy is the issue. - Ignoring SCPs. If the account is in an AWS Organization, an SCP may be denying the action at the org level. The IAM Policy Simulator will not catch this.
- Assuming the simulator equals production. The simulator does not evaluate SCPs, VPC endpoint policies, or all resource-based policy scenarios. A simulator “allowed” confirms identity policy logic only.
- Testing the wrong resource ARN in the simulator. The simulator uses the ARN you give it exactly. If the actual request targets a different path or object key, the simulation result may not match the live behavior.
- Confusing an AssumeRole denial with a downstream action denial. If
sts:AssumeRoleis denied, the fix is in the trust policy or the source principal’s identity policy, not in the target role’s permissions policies. - Treating an S3 403 as a pure IAM problem. If the error comes from S3 and does not include a principal ARN, the cause may be a bucket policy, Block Public Access, or Object Lock rather than a missing IAM Allow.
- Ignoring session policies in brokered-access flows. If the role was assumed with an inline session policy, the resulting credentials are narrower than the role’s own policies. Check what policy document was passed to
sts:AssumeRole.
Summary
- Extract three things from every error: the principal ARN, the denied action, and the target resource.
- When the ARN says
assumed-role, check the role, not the user who assumed it. - Work through all policy layers in order: identity policy, permissions boundary, session policy, SCP, resource-based policy, VPC endpoint policy, then trust policy for AssumeRole failures.
- An explicit deny in any layer wins over any Allow. Find and fix it rather than trying to add more Allows.
- Use
aws iam simulate-principal-policyto confirm identity policy logic, then verify with a real API call. The simulator does not evaluate SCPs or all resource-based policy scenarios. - For S3 403 errors without a principal ARN, switch to the S3-specific troubleshooting guide. The cause is often a bucket policy or Block Public Access, not a missing IAM Allow.
Frequently asked questions
How do I know whether the deny is from IAM or from the service itself?
IAM AccessDenied errors include the principal ARN and say "is not authorized to perform: ACTION on resource: RESOURCE". Service-level denials look different: S3 returns an HTTP 403 with a plain "Access Denied" body that does not mention a principal ARN. If you see the principal ARN in the message, it is an IAM evaluation result. If you do not, check service-specific settings such as S3 bucket policies or Block Public Access.
What does implicit deny mean in IAM?
Implicit deny means no Allow policy covers the requested action. The absence of permission results in denial. Explicit deny means a Deny statement directly blocked the request. Both produce an AccessDenied error, but explicit denies cannot be overridden by any Allow, even one from a resource-based policy in another account.
Why can a role still fail even when its inline policy looks correct?
Several layers can block an action independently of the identity-based policy: a permissions boundary might exclude the action, an SCP from AWS Organizations might deny it at the org level, a session policy might restrict the credentials, or a resource-based policy on the target resource might have an explicit deny. Check all layers, not just the role policy.
What is the difference between an sts:AssumeRole deny and an action deny like s3:PutObject?
An sts:AssumeRole denial happens before your code runs. The principal cannot get credentials for the target role at all. An action denial like s3:PutObject happens after the role is assumed, when the role tries to use those credentials to call a service. AssumeRole failures appear at the STS call; action failures appear inside the service call in CloudWatch Logs or your application error output.
Can the IAM Policy Simulator prove that production will work?
No. The simulator tests identity-based policies and permissions boundaries against the policy document as it exists today. It does not evaluate SCPs from AWS Organizations, VPC endpoint policies, or all resource-based policy scenarios. A simulator result of "allowed" means the identity policy permits the action. It does not guarantee the live request will succeed.