Skip to content

Fix: AWS AccessDeniedException when calling an AWS service operation

FixDevs · (Updated: )

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:DescribeInstances

Or variations like:

An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
An error occurred (UnauthorizedAccess) when calling the AssumeRole operation:
User: arn:aws:sts::123456789012:assumed-role/my-role/session is not authorized to perform: sts:AssumeRole
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the Invoke operation

AWS 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 AccessDenied could come from a resource the caller did not own. The lesson: check aws s3api get-bucket-policy even 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-user started returning a PermissionsBoundary field. 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. AccessDenied can now mean “your principal has no Department=Eng tag” rather than “you have no permission.”

  • Nov 2019 — aws sts decode-authorization-message. EC2 error messages started shipping an encoded Encoded authorization failure message blob. 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 AccessDenied traces back to the permission set definition in the management account, not the role in the workload account.

  • 2023 — aws_iam policy 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-identity

Output:

{
    "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 staging

Set AWS_PROFILE in 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:DescribeInstances

This means you need the ec2:DescribeInstances permission. Add it to your IAM user or role.

Using the AWS Console:

  1. Go to IAM → Users (or Roles) → Select your identity
  2. Click “Add permissions” → “Attach policies directly”
  3. Search for the relevant managed policy (e.g., AmazonEC2ReadOnlyAccess)
  4. Attach it

Using the AWS CLI:

aws iam attach-user-policy \
  --user-name myuser \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess

Using 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 myuser

For roles:

aws iam list-attached-role-policies --role-name my-role
aws iam list-role-policies --role-name my-role

Download 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 Allow policies when an explicit Deny exists. 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:

  1. Go to https://policysim.aws.amazon.com
  2. Select the user or role
  3. Choose the service and action
  4. 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:DescribeInstances

This 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-bucket

A 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 myuser

Look 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:

  1. The role’s trust policy in the target account must trust the source account.
  2. The user/role in the source account must have sts:AssumeRole permission 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 5

Or search in CloudTrail Event History in the AWS Console. Filter by:

  • Event name: The API action
  • Error code: AccessDenied or UnauthorizedAccess
  • 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 IPs
  • aws:MultiFactorAuthPresent — requires MFA
  • aws:PrincipalTag — requires specific tags on the identity
  • aws:RequestedRegion — restricts to specific regions
  • aws: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 -50

This 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.tool

The 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles