Fix: AWS AccessDeniedException when calling an AWS service operation
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix AWS AccessDeniedException caused by missing IAM permissions, explicit denies, SCPs, resource policies, permission boundaries, and misconfigured roles.
The Error
You call an AWS API and get:
An error occurred (AccessDeniedException) when calling the DescribeInstances operation:
User: arn:aws:iam::123456789012:user/myuser is not authorized to perform: ec2:DescribeInstancesOr variations like:
An error occurred (AccessDenied) when calling the PutObject operation: Access DeniedAn error occurred (UnauthorizedAccess) when calling the AssumeRole operation:
User: arn:aws:sts::123456789012:assumed-role/my-role/session is not authorized to perform: sts:AssumeRolebotocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the Invoke operationAWS refused your request because the identity making the call does not have permission to perform the action on the target resource.
Why This Happens
AWS uses IAM (Identity and Access Management) to control who can do what. Every API call is evaluated against IAM policies. A request is allowed only if there is an explicit Allow and no explicit Deny. The evaluation walks a fixed order: explicit deny, then organization SCPs, then resource-based policy, then identity-based policy, then permission boundary, then session policy. A deny at any layer terminates the chain — there is no “outvoting” a deny by stacking more allows on top of it.
The wording of the error also matters. AccessDenied (without “Exception”) is what S3 returns; AccessDeniedException is the standard format for most other services (IAM, Lambda, DynamoDB, ECS). UnauthorizedOperation is what EC2 specifically uses for actions you cannot perform, and it sometimes ships with an encoded message you can decode with aws sts decode-authorization-message. Different SDKs surface these slightly differently — botocore raises ClientError, the Java SDK raises AmazonServiceException, and the JavaScript SDK v3 throws an error whose name property is the service-specific code. Match the code to the service before assuming all “access denied” errors are the same problem.
The most common causes:
- Missing IAM policy. Your user or role does not have a policy granting the required permission.
- Explicit deny. A policy explicitly denies the action, overriding any allows.
- Service Control Policy (SCP). An organization-level policy restricts the action for the entire account.
- Resource-based policy. The target resource (S3 bucket, SQS queue, Lambda function) has a policy that blocks your identity.
- Permission boundary. The IAM user or role has a permission boundary that limits its effective permissions.
- Wrong identity. You are authenticated as a different user or role than you expect.
- Cross-account access. You are trying to access a resource in a different AWS account without proper trust configuration.
- Condition key mismatch. The policy allows the action but only under specific conditions (IP range, MFA, tags) that your request doesn’t meet.
- Eventual consistency on freshly created policies. IAM is globally distributed and a brand-new permission can take several seconds to be visible at the API endpoint you are calling.
Version History That Changes the Failure Mode
The shape of “AccessDenied” has shifted as AWS added new policy types over the years. Knowing what each control was introduced for prevents you from chasing the wrong layer:
2011 — IAM general availability. Identity policies were the only access control. If you got
AccessDenied, the fix was always “add permissions to the user.” Many older blog posts still assume this single-layer model.2014 — Cross-account resource-based policies for S3, SNS, SQS. This was the first time
AccessDeniedcould come from a resource the caller did not own. The lesson: checkaws s3api get-bucket-policyeven when your IAM policy looks complete.Feb 2017 — Service Control Policies (SCPs) launch with AWS Organizations. Before SCPs, a root or admin user could never be denied at the account level. After SCPs, even a full-admin role can be blocked by a guardrail set at the OU. SCPs are invisible from inside the account — you have to ask the org admin.
Jul 2018 — Permission Boundaries. Boundaries are an upper-bound filter on what an identity-based policy can grant.
aws iam get-userstarted returning aPermissionsBoundaryfield. If a developer creates a new policy and the action still fails, the boundary is the next place to look.Nov 2018 — Attribute-Based Access Control (ABAC) with
aws:PrincipalTag. Policies began using condition keys that compare tags on the calling principal to tags on the resource.AccessDeniedcan now mean “your principal has noDepartment=Engtag” rather than “you have no permission.”Nov 2019 —
aws sts decode-authorization-message. EC2 error messages started shipping an encodedEncoded authorization failure messageblob. Decoding it shows which specific statement evaluated to deny:aws sts decode-authorization-message --encoded-message "<long-base64-blob>"If you are still grepping the human-readable error in 2026, you are leaving half the diagnostic on the table.
Jul 2022 — IAM Identity Center (renamed from AWS SSO). The rename matters because old policies, runbooks, and Terraform modules still reference the SSO names. Permission sets in Identity Center are realized as IAM roles per account, so
AccessDeniedtraces back to the permission set definition in the management account, not the role in the workload account.2023 —
aws_iampolicy size limits raised; condition keys for IPv6. Several long-standing limits (managed policy length, number of policies per role) changed. Tooling that pre-dates the increase can still trip on the old limits if it was hard-coded.
Fix 1: Verify Your Identity
Before debugging policies, confirm who AWS thinks you are:
aws sts get-caller-identityOutput:
{
"UserId": "AIDAEXAMPLEUSERID",
"Account": "123456789012",
"Arn": "arn:aws:iam::123456789012:user/myuser"
}Check:
- Is this the right user/role? You might be using the wrong AWS profile or credentials.
- Is this the right account? You might be in a sandbox or staging account.
- Is this an assumed role? If the ARN shows
assumed-role, you are using temporary credentials from an assumed role, which may have different permissions than your IAM user.
If the identity is wrong, fix your credentials. See Fix: AWS Unable to locate credentials for troubleshooting credential configuration.
Pro Tip: Use named AWS profiles to avoid accidentally using the wrong credentials:
aws sts get-caller-identity --profile production aws s3 ls --profile stagingSet
AWS_PROFILEin your shell to change the default, rather than hardcoding credentials.
Fix 2: Check and Add Missing Permissions
Find out which permission you need. The error message usually tells you:
is not authorized to perform: ec2:DescribeInstancesThis means you need the ec2:DescribeInstances permission. Add it to your IAM user or role.
Using the AWS Console:
- Go to IAM → Users (or Roles) → Select your identity
- Click “Add permissions” → “Attach policies directly”
- Search for the relevant managed policy (e.g.,
AmazonEC2ReadOnlyAccess) - Attach it
Using the AWS CLI:
aws iam attach-user-policy \
--user-name myuser \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccessUsing a custom inline policy:
aws iam put-user-policy \
--user-name myuser \
--policy-name EC2DescribeAccess \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "ec2:DescribeInstances",
"Resource": "*"
}]
}'Note: For production, prefer managed policies or shared custom policies over inline policies. Inline policies are harder to audit and manage at scale.
Fix 3: Check for Explicit Denies
An explicit Deny in any policy always wins over Allow. Check all policies attached to your identity:
aws iam list-attached-user-policies --user-name myuser
aws iam list-user-policies --user-name myuser
aws iam list-groups-for-user --user-name myuserFor roles:
aws iam list-attached-role-policies --role-name my-role
aws iam list-role-policies --role-name my-roleDownload and inspect each policy. Look for "Effect": "Deny" statements:
{
"Effect": "Deny",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}This policy denies all EC2 actions outside us-east-1. Even if another policy allows ec2:DescribeInstances, the deny overrides it.
Common Mistake: Adding more
Allowpolicies when an explicitDenyexists. The deny always wins. You must remove or modify the deny policy, not add more allows.
Fix 4: Use the IAM Policy Simulator
The IAM Policy Simulator evaluates your policies without making real API calls:
- Go to https://policysim.aws.amazon.com
- Select the user or role
- Choose the service and action
- Click “Run Simulation”
The simulator tells you whether the action is allowed or denied and which policy is responsible.
From the CLI:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/myuser \
--action-names ec2:DescribeInstancesThis shows which policies allow or deny the action and why.
Fix 5: Check Service Control Policies (SCPs)
If your AWS account is part of an AWS Organization, SCPs can restrict permissions at the account or OU (Organizational Unit) level. SCPs act as a guardrail — even if your IAM policy allows an action, the SCP can block it.
Check with your organization administrator. SCPs are managed at the organization level and are not visible to individual account users.
Common SCP restrictions:
- Region restrictions: Only allow actions in specific regions
- Service restrictions: Block access to certain services entirely
- Root user restrictions: Prevent root from performing certain actions
Fix 6: Check Resource-Based Policies
Some AWS resources have their own policies that control access independently of IAM:
- S3 bucket policies
- SQS queue policies
- SNS topic policies
- Lambda function policies
- KMS key policies
For S3 AccessDenied errors specifically, see Fix: AWS S3 Access Denied.
Check the resource policy:
aws s3api get-bucket-policy --bucket my-bucketA bucket policy might explicitly deny access:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalOrgID": "o-12345example"
}
}
}This denies access to anyone outside the organization, even if IAM policies allow it.
Fix 7: Check Permission Boundaries
Permission boundaries are IAM policies attached to users or roles that set the maximum permissions. Even if an attached policy allows an action, the permission boundary must also allow it.
Check if a permission boundary is set:
aws iam get-user --user-name myuserLook for the PermissionsBoundary field in the output. If present, the effective permissions are the intersection of the attached policies and the permission boundary.
Fix 8: Fix Assume Role Issues
If the error happens when assuming a role, check the role’s trust policy:
aws iam get-role --role-name my-role --query 'Role.AssumeRolePolicyDocument'The trust policy must explicitly allow your identity to assume the role:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:user/myuser"
},
"Action": "sts:AssumeRole"
}]
}For cross-account role assumption, both sides need configuration:
- The role’s trust policy in the target account must trust the source account.
- The user/role in the source account must have
sts:AssumeRolepermission for the target role.
Fix 9: Debug with CloudTrail
CloudTrail logs every API call and its authorization result. Find the denied request:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=DescribeInstances \
--max-results 5Or search in CloudTrail Event History in the AWS Console. Filter by:
- Event name: The API action
- Error code:
AccessDeniedorUnauthorizedAccess - User name: Your identity
The CloudTrail event shows the exact ARN that made the request, the resource targeted, and the error. This is the definitive source for debugging access issues.
For SSL/TLS errors that prevent API calls from reaching AWS at all, see Fix: SSL certificate problem.
Still Not Working?
If you have checked everything above and still get AccessDeniedException:
Check VPC endpoints. If your service uses a VPC endpoint, the endpoint’s policy might restrict which IAM identities can use it. Check the endpoint policy in the VPC console.
Check condition keys. Some policies use conditions that aren’t obvious:
aws:SourceIp— only allows access from specific IPsaws:MultiFactorAuthPresent— requires MFAaws:PrincipalTag— requires specific tags on the identityaws:RequestedRegion— restricts to specific regionsaws:CalledVia— restricts which service can make the call
Check for recently created policies. IAM policy changes can take a few seconds to propagate. If you just added a policy, wait 10-30 seconds and retry.
Check for service-linked roles. Some AWS services require service-linked roles that must be created separately. If a service can’t create the role automatically, you get AccessDeniedException.
Check Terraform state locks. If the AccessDeniedException happens during Terraform operations, it might be related to state lock issues on the DynamoDB lock table rather than the IAM action you intended to perform. Inspect the failing call in TF_LOG=DEBUG output — if the action is dynamodb:PutItem against a terraform-locks table, the deny is about locking, not about your target resource.
Check the AWS service quotas. Some services return AccessDeniedException when you hit service quotas instead of a quota-specific error. Check Service Quotas in the AWS Console.
Use the --debug flag for detailed request information:
aws ec2 describe-instances --debug 2>&1 | tail -50This shows the exact request, headers, and response, including any error details not shown in the standard output.
Decode the EC2 encoded authorization message. EC2 routinely returns an Encoded authorization failure message that contains the exact failing statement and the principal under evaluation. Decode it:
aws sts decode-authorization-message \
--encoded-message "<the-encoded-blob-from-the-error>" \
--query DecodedMessage --output text | python -m json.toolThe output names the principal, the action, the resource, the matched and unmatched statements, and the explicit allow/deny verdict. For EC2 calls this is almost always faster than running the policy simulator.
Verify the role session is still valid. Temporary credentials from AssumeRole expire (default 1 hour, max 12 with DurationSeconds). An expired session returns ExpiredToken, but some SDKs catch the refresh failure and surface it as AccessDeniedException. Re-run aws sts get-caller-identity immediately before the failing call to confirm the session is alive.
Check for region-specific endpoints. A few services (IAM itself, CloudFront, Route 53) are global but have to be called against us-east-1. Other services have endpoints that vary by partition (aws, aws-cn, aws-us-gov). Calling the wrong endpoint can return AccessDenied rather than a routing error. Set AWS_REGION explicitly and check the endpoint in the SDK log.
Look for new SCPs deployed in the last hour. SCP changes propagate within seconds but cached identity tokens can still hold a stale evaluation result for a few minutes. If a previously working call started failing without any code change, ask the organization admin to check the OU’s SCP history.
If network connectivity itself is the problem rather than permissions, see Fix: curl failed to connect for troubleshooting network issues to AWS endpoints.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Bedrock Not Working — Model Access, IAM, Converse API, Streaming, and Cross-Region
How to fix AWS Bedrock errors — AccessDeniedException for model access, bedrock vs bedrock-runtime client, Converse vs InvokeModel API, streaming with ConverseStream, regional availability, and Knowledge Bases setup.
Fix: AWS Lambda Layer Not Working — Module Not Found or Layer Not Applied
How to fix AWS Lambda Layer issues — directory structure, runtime compatibility, layer ARN configuration, dependency conflicts, size limits, and container image alternatives.
Fix: AWS SQS Not Working — Messages Not Received, Duplicate Processing, or DLQ Filling Up
How to fix AWS SQS issues — visibility timeout, message not delivered, duplicate messages, Dead Letter Queue configuration, FIFO queue ordering, and Lambda trigger problems.
Fix: AWS S3 CORS Error — Access to Fetch Blocked by CORS Policy
How to fix AWS S3 CORS errors — S3 bucket CORS configuration, pre-signed URL CORS, CloudFront CORS headers, OPTIONS preflight requests, and presigned POST uploads.