AWS Permission Denied Errors: How to Fix AccessDenied, UnauthorizedOperation, and 403

AWS returns permission denied errors in several forms: AccessDenied, UnauthorizedOperation, HTTP 403. The cause is not always where you expect it. The denial might come from an IAM identity policy, a resource policy, a Service Control Policy, a permission boundary, a session policy, or a simple wrong-account mistake. This page is a hub for diagnosing any of those causes quickly.

Before you touch any policies, confirm who is actually making the request, extract the denied action and resource from the error message, and check CloudTrail. Most permission denied issues are resolved in under 10 minutes once you know which layer is responsible.

Simple explanation

Analogy

Think of AWS authorization like a nightclub with multiple checkpoints. The bouncer at the door (your IAM policy) has a guest list. But there’s also a fire marshal (the SCP) who can block the whole building from operating in certain ways, a VIP coordinator (the permission boundary) who caps what any guest can do, and a private room with its own doorman (the resource policy). Having your name on the guest list only gets you past the first checkpoint. Every checkpoint must let you through.

A permission denied error in AWS means the request reached AWS successfully, but the authorization engine decided the caller was not allowed to perform that action. This is an authorization problem, not an authentication problem. Your credentials are valid. AWS just will not let you do that specific thing.

The same root cause can appear as three different error shapes:

  • AccessDeniedException — returned by IAM and most AWS services
  • UnauthorizedOperation — the EC2-specific name for the same error
  • HTTP 403 Access Denied — returned by S3 and some API Gateway endpoints

One rule applies universally: an explicit Deny in any policy overrides any number of Allows. It does not matter how many Allow statements exist. One Deny anywhere in the evaluation chain wins.

When to use this page

Use this page when:

  • A CLI or SDK call returns AccessDeniedException or UnauthorizedOperation
  • A console action is blocked with “You are not authorized to perform this action”
  • S3 returns 403 Access Denied and you are not sure whether IAM or the bucket policy is responsible
  • Code works locally under your user credentials but fails in Lambda, ECS, or a CI/CD pipeline
  • You can see an Allow in the IAM policy but the request is still denied
  • You have just assumed a role and actions that worked before are now failing

If you already know the problem is specifically in IAM policy logic, go directly to the IAM AccessDenied guide. If the error is clearly from S3 bucket policy or Block Public Access, see the S3 Access Denied guide. If the issue is expired or missing credentials rather than permissions, see Troubleshooting Authentication Errors.

5-minute diagnosis checklist

Start here

Run step 1 before anything else. A large percentage of permission denied issues are caused by running as the wrong identity — a different profile, a role instead of a user, or the wrong account entirely.

  1. Confirm the caller identity. Run aws sts get-caller-identity. Verify the account, ARN, and user or role match what you expect.
  2. Extract the denied action and resource from the error. The error message typically contains the calling identity, the action (e.g. ec2:DescribeInstances), and the resource ARN.
  3. Decide which category applies. Is this an IAM-only action, an S3-specific denial, or a cross-account call? The category changes where you look next.
  4. Check CloudTrail for the denied event. CloudTrail records all denied requests, and the event sometimes reveals SCP-level denials that the error message alone does not show.
  5. Check for SCPs, permission boundaries, and session policies. If the IAM policy looks correct, one of these layers is usually responsible.
  6. Test with the IAM Policy Simulator. Simulate the exact action and resource against the principal that failed.
  7. Confirm region, account, and profile assumptions. Many denials are caused by running in the wrong region or under the wrong profile.

Error message patterns and what they usually mean

Error text or patternUsually meansFirst thing to check
AccessDeniedException: User is not authorized to perform: ACTIONIdentity policy is missing the Allow, or an explicit Deny is blocking itIAM policy attached to the user or role
UnauthorizedOperationSame as AccessDenied, EC2 API variantIAM policy, then CloudTrail for SCP context
S3 403 Access DeniedBucket policy, Block Public Access, or IAM policy is blocking the requestBucket policy, IAM identity policy, Block Public Access settings
sts:AssumeRole deniedCaller lacks permission to assume the role, or the role’s trust policy does not allow the callerIAM policy on the caller (allow sts:AssumeRole) and trust policy on the target role
iam:PassRole deniedCode is trying to pass a role to a service but the caller lacks iam:PassRole on that role ARNIAM policy must include iam:PassRole with a Resource matching the specific role ARN
"is not authorized to perform"Generic IAM-layer denial, could be identity policy, SCP, or boundaryRun the IAM Policy Simulator against the exact principal and action
IAM policy says Allow but access is still deniedSCP or permission boundary is overriding the identity policyCloudTrail event details and SCPs attached to the account’s OU

How AWS authorization works

Every AWS API request goes through a multi-layer evaluation before it is allowed or denied. Understanding the order tells you where to look.

AWS evaluates in this sequence:

  1. Explicit Deny — Any explicit Deny anywhere (identity policy, resource policy, SCP, permission boundary) immediately denies the request. Nothing overrides it.
  2. Service Control Policy (SCP) — If the account belongs to an AWS Organization, the SCPs attached to the account’s OU set a ceiling on what is possible. An SCP cannot grant permissions. It can only restrict them. Even a full Allow * in IAM is constrained by the SCP.
  3. Resource policy — For services that support resource-based policies (S3 buckets, KMS keys, SQS queues), the resource policy is evaluated. For same-account calls, either the identity policy or the resource policy can grant access. For cross-account calls, both must grant access.
  4. Identity policy — Policies attached to the IAM user, group, or role must explicitly Allow the action.
  5. Permission boundary — If a permission boundary is attached to the role or user, the action must also fall within the boundary. A boundary limits the maximum scope of what the identity policy can grant. See IAM policy structure for how boundaries interact with identity policies.
  6. Session policy — If the call is made using temporary credentials from STS, the session policy attached at assume-role time must also allow the action.
The one rule that trips everyone up

You can have a valid Allow at step 4 and still be denied at step 2 because an SCP restricted the action. The SCP layer is invisible from inside the member account. If the IAM policy looks correct, always check CloudTrail for SCP context before touching any policy documents.

Most common root causes

Missing Allow in the identity policy#

The most common case. The IAM policy attached to the user or role does not include the specific API action being called. Some actions look similar but are distinct: s3:GetObject does not grant s3:GetObjectAcl, and ec2:DescribeInstances does not grant ec2:StartInstances.

Symptom: User is not authorized to perform: ACTION on resource: RESOURCE

Check first: IAM, then Users or Roles, Permissions tab, and look at which policies are attached and what actions they include.

Fix: Add the missing action to the relevant policy, following least-privilege principles.

Wrong resource ARN#

Policies that restrict to a specific resource ARN must match exactly. A policy that allows arn:aws:s3:::my-bucket/* does not cover actions on the bucket itself (arn:aws:s3:::my-bucket). A policy scoped to us-east-1 resources does not cover eu-west-1 resources.

Symptom: The action is present in the policy, but the resource condition does not match the ARN in the request.

Check first: Compare the ARN in the error message against the ARN in the policy’s Resource field.

Fix: Correct the ARN in the policy, or add a second statement covering the missing ARN pattern.

Explicit Deny in a resource policy#

Resource-based policies on S3 buckets, KMS keys, and SQS queues can contain explicit Deny statements. These override any Allow in the identity policy. A developer can have full s3:* on their user and still be blocked by a bucket policy that denies specific principals.

Symptom: Identity policy has an Allow, but the request is still denied.

Check first: The resource policy attached to the S3 bucket, KMS key, or other target resource.

Fix: Remove or narrow the Deny in the resource policy, or add an exception condition.

Service Control Policy (SCP) restriction#

Hidden blocker

SCPs are not visible from inside the member account. Developers can see their IAM policies but cannot see SCPs. If the IAM policy is correct and the request is still denied, check CloudTrail for “service control policy” in the error before editing any IAM document.

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.

Symptom: IAM policy is correct. CloudTrail errorMessage says “implicit deny in service control policy.”

Check first: AWS Organizations, then Policies, then Service Control Policies attached to your account’s OU.

Fix: Update the SCP to allow the action (requires organization management access), or move the account to a different OU.

Permission boundary restriction#

Permission boundaries cap what an identity policy can grant. If a role has a permission boundary that does not include s3:PutObject, no identity policy can grant that action to that role, even if the identity policy has Allow s3:* on *.

Symptom: Role has a clear Allow, but requests fail anyway. The role was created by an automated tool like CDK, Terraform, or Service Catalog.

Check first: IAM, then Roles, select the role, Permissions tab, and look for a “Permissions boundary” section.

Fix: Update the boundary policy to include the required action, or remove the boundary if it is no longer needed.

Session policy or temporary credential restriction#

When code assumes a role, it can optionally pass a session policy that further restricts the resulting permissions. The effective permissions are the intersection of the role’s identity policy, the role’s permission boundary, and the session policy. The most restrictive combination wins.

If you are using STS temporary credentials and your code passed a session policy at assume-role time, check that the session policy does not narrow away the action you need.

Symptom: Role policy has Allow, but Lambda or CI/CD pipeline is denied. Permissions are narrower than expected.

Check first: The assume-role call in your code. Does it pass a Policy parameter?

Fix: Remove or broaden the session policy, or ensure the intersection of role policy and session policy covers the required actions.

Wrong account, wrong profile, wrong region#

Many denials happen because the caller is in a different account or region than intended. A resource ARN in us-east-1 does not exist from eu-west-1. A resource in account A does not accept identity policies from account B unless cross-account access is explicitly configured.

Symptom: Permissions look correct, but the resource does not appear to exist or the ARN does not resolve.

Check first: aws sts get-caller-identity to confirm account ID, and check the --region flag or AWS_DEFAULT_REGION environment variable.

Fix: Switch profile or region. Configure cross-account access if the resource is genuinely in a different account.

Assumed role confusion#

Most common mistake in CI/CD and Lambda setups

When code assumes a role, it operates under the role’s permissions only. It does not retain the permissions of the original user. Adding the permission to your personal IAM user does nothing when the code runs as a Lambda execution role.

Symptom: Works locally under your user credentials. Fails in Lambda, ECS, or CI/CD.

Check first: aws sts get-caller-identity in the failing environment. Confirm which role is actually running the code.

Fix: Add the required permission to the execution role. See role assumption in AWS for how this works.

S3 bucket policy and access model issues#

S3 has its own layered evaluation that includes IAM identity policies, bucket policies, and Block Public Access settings. These interact in ways that differ from other services. An IAM Allow for s3:GetObject can be denied by a bucket policy Deny, and Block Public Access can override even an explicit bucket policy Allow for anonymous access.

Symptom: S3 returns 403 Access Denied and the IAM policy looks correct.

Check first: The bucket policy (not just the IAM policy), and Block Public Access settings on both the bucket and the account.

Fix: See the S3 Access Denied guide and S3 IAM vs bucket policies for the full evaluation model.

Step-by-step troubleshooting workflow

Step 1: Confirm who is making the request

aws sts get-caller-identity

This returns the account ID, user ID, and ARN of the identity making the call. Many denials are solved here — the wrong role is running, or the wrong profile is active.

Step 2: Extract the denied action and resource from the error

The error message typically contains:

User: arn:aws:sts::111122223333:assumed-role/my-role/session-name
is not authorized to perform: ec2:DescribeInstances
on resource: *

Write down the identity ARN, the action, and the resource ARN. These three facts drive every subsequent step.

Step 3: Look up the denied event in CloudTrail

CloudTrail records all API calls, including denied ones. The event often contains more detail than the error message, particularly whether an SCP was responsible.

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=ErrorCode,AttributeValue=AccessDenied \
  --start-time <START_TIME_ISO8601>

In the console: CloudTrail, Event History, filter by Error Code AccessDenied or UnauthorizedOperation. Open the event and check the errorMessage field.

Step 4: Run the IAM Policy Simulator

Test the exact action and resource against the failing principal:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:role/my-role \
  --action-names ec2:DescribeInstances \
  --resource-arns "*"

The simulator shows which policy allowed or denied the action, including permission boundary evaluation.

Step 5: Check SCPs if the account is in an organization

If CloudTrail shows “service control policy” in the error, or the IAM policy looks correct but access is still denied:

  • Go to AWS Organizations, then Policies, then Service Control Policies.
  • Find which SCPs are attached to the account’s OU.
  • Look for Deny statements or missing Allow statements covering the action.

This step requires access to the organization management account or a delegated administrator account for Organizations.

Step 6: Check permission boundaries on roles

In IAM, go to Roles, select the role, and open the Permissions tab. If there is a “Permissions boundary” section, the boundary must include the action. If the boundary policy uses a restricted action set, add the action there.

Step 7: Check session policy or STS context

If the call is made with temporary credentials, check whether the assume-role call in your code passes a session policy parameter. The effective permission is the intersection of the role policy and the session policy.

Step 8: For S3, check the bucket policy and Block Public Access separately

aws s3api get-bucket-policy --bucket my-bucket
aws s3api get-public-access-block --bucket my-bucket

S3 evaluates these independently from IAM. A bucket policy Deny blocks access even if the IAM policy Allows. See S3 IAM vs bucket policies for the full evaluation logic.

Real-world examples

Pattern to notice

In each example below, the developer’s first instinct is to check the IAM policy. In two of the three cases, the IAM policy is actually fine. The real cause is in a different layer. This is the core skill in AWS permission debugging: knowing which layer to look at before you start editing.

Example 1: EC2 UnauthorizedOperation caused by SCP region restriction#

A developer runs aws ec2 describe-instances --region eu-west-1 and gets UnauthorizedOperation. Her IAM policy includes:

{
  "Effect": "Allow",
  "Action": "ec2:DescribeInstances",
  "Resource": "*"
}

The policy looks correct. CloudTrail shows the event, and the errorMessage field reads: "Implicit deny in service control policy".

Checking the SCPs on her account’s OU in AWS Organizations reveals:

{
  "Effect": "Deny",
  "Action": "ec2:*",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "aws:RequestedRegion": ["us-east-1", "us-west-2"]
    }
  }
}

The SCP restricts EC2 to us-east-1 and us-west-2. Her IAM policy is fine. The SCP is the blocker. Fix: run the command in an allowed region, or request an SCP update from the organization team.

Example 2: S3 403 caused by bucket policy cross-account conflict#

A Lambda function has an execution role with s3:GetObject Allow on arn:aws:s3:::reports-bucket/*. The function gets 403 Access Denied when reading from reports-bucket.

The IAM policy is correct. Checking the bucket policy reveals:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:GetObject",
  "Resource": "arn:aws:s3:::reports-bucket/*",
  "Condition": {
    "StringNotEquals": {
      "aws:PrincipalAccount": "999988887777"
    }
  }
}

The bucket policy denies all principals whose account ID is not 999988887777. The Lambda role is in account 111122223333. The Deny wins regardless of what IAM says.

Fix: Update the bucket policy Condition to include the Lambda’s account ID, or add the Lambda’s role ARN as an explicit Allow. This is a classic cross-account S3 access pattern. See S3 IAM vs bucket policies for how these two policies interact.

Example 3: AssumeRole denied in a Terraform CI/CD pipeline#

A Terraform pipeline fails with:

Error: User: arn:aws:iam::111122223333:role/ci-runner-role
is not authorized to perform: sts:AssumeRole
on resource: arn:aws:iam::444455556666:role/terraform-deploy-role

The pipeline’s execution role (ci-runner-role) is trying to assume a deployment role in another account (terraform-deploy-role). Two things must both be true for this to work:

  1. ci-runner-role must have sts:AssumeRole Allow on the target role ARN in its IAM policy.
  2. terraform-deploy-role in account 444455556666 must have a trust policy that allows ci-runner-role in account 111122223333 to assume it.

If either is missing, you get this error. Check ci-runner-role’s IAM policy for the sts:AssumeRole permission, and check terraform-deploy-role’s trust policy for the correct principal ARN.

See role assumption in AWS for how trust policies work, and the Terraform Permission Errors guide for patterns specific to Terraform deployments.

Permission denied vs authentication errors

These are different problems with different fixes. Misdiagnosing them wastes time.

Permission denied (authorization)Authentication error
What it meansCredentials are valid, but the action is not allowedCredentials are missing, expired, or invalid
Typical errorAccessDeniedException, UnauthorizedOperation, 403InvalidClientTokenId, ExpiredTokenException, NoCredentialProviders
How to confirmaws sts get-caller-identity returns an identityaws sts get-caller-identity itself fails
Where to lookIAM policies, SCPs, resource policies, boundariesAWS credential file, environment variables, instance metadata
Fix directionUpdate or add policiesRefresh or reconfigure credentials
Quick test

Run aws sts get-caller-identity. If it returns an account ID and ARN, you are authenticated. The problem is authorization. If it fails or returns an error, the problem is authentication. See Troubleshooting Authentication Errors for that path.

This page vs more specific guides

SituationBest guide
Generic permission denied, not sure where to startThis page
IAM policy is definitely the issue, need detailed IAM debuggingIAM AccessDenied Errors
S3 403 or bucket policy or Block Public AccessS3 Access Denied Errors
Expired token, missing credentials, InvalidClientTokenIdAuthentication Errors
Terraform deploy is blocked by permissionsTerraform Permission Errors

Common mistakes

  1. Debugging the wrong identity. Checking your IAM user’s permissions when the code actually runs as a Lambda execution role. Always run aws sts get-caller-identity in the failing environment, not locally.
  2. Assuming Resource: ”*” solves everything. Some actions require a specific resource type. iam:PassRole needs a role ARN, not *. Some actions like s3:ListAllMyBuckets cannot be scoped to a resource at all.
  3. Missing the SCP layer. Developers can view their IAM policies but often cannot see SCPs. If the IAM policy is correct, check CloudTrail for SCP-related denial messages before editing anything.
  4. Ignoring permission boundaries on roles. Roles created by automated tools (CDK, Terraform, Service Catalog) sometimes include permission boundaries that silently cap what the role can do, even when the identity policy has a broad Allow.
  5. Confusing authorization errors with authentication errors. Trying to fix credentials when the problem is actually policy-based. Run aws sts get-caller-identity. If it succeeds, you are authenticated and the problem is authorization.
  6. Forgetting that S3 evaluates differently. S3 uses both IAM identity policies and bucket policies, and Block Public Access can override both. A fix to the IAM policy alone may not resolve an S3 403.
  7. Forgetting ARN exactness. A policy allowing arn:aws:s3:::my-bucket/* does not cover actions on arn:aws:s3:::my-bucket (the bucket itself). Both ARN forms are often needed.

Frequently asked questions

Can I have the right IAM policy and still get a permission denied error?

Yes. A Service Control Policy at the organization level, a permission boundary, a resource-based policy with an explicit Deny, or a session policy restriction can all block access even when your identity policy has an Allow. The IAM evaluation engine checks every layer, and an explicit Deny anywhere overrides any number of Allows.

What is the difference between AccessDenied and UnauthorizedOperation?

Same meaning, different naming convention. AccessDeniedException is returned by IAM and most AWS services. UnauthorizedOperation is the EC2-specific name for the same error. Both mean the IAM evaluation engine rejected the request.

How do I tell whether the denial came from an SCP?

Check the CloudTrail event for the denied call. The errorMessage field sometimes says "implicit deny in service control policy" or similar. The IAM Policy Simulator also shows SCP evaluation results when you test the principal against its organization policies.

Why does my CLI work but my Lambda or CI/CD pipeline fail?

Your CLI runs as your IAM user or a locally configured profile. Lambda runs as an execution role. CI/CD pipelines run as a role or service account. These identities have different permissions. Always confirm the identity your code actually uses by calling aws sts get-caller-identity inside the failing environment.

Is S3 access denied the same as IAM access denied?

Not exactly. S3 evaluates its own bucket policy and Block Public Access settings in addition to IAM policies. A user can have an IAM Allow for s3:GetObject and still be denied if the bucket policy has an explicit Deny, or if Block Public Access is enabled. For S3-specific denials, check both the IAM identity policy and the bucket policy.

Last verified: 13 May 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.