AWS Access Denied With an Explicit Deny: Finding the Policy That Blocks You

Logeshwaran.C

If your AWS request fails with "explicit deny," the fix is to find the exact policy carrying that Deny statement — an identity-based policy, a resource-based policy, a Service Control Policy, a Resource Control Policy, a permissions boundary, or a session policy — because a Deny in any one of them beats every Allow everywhere else, no matter how much access you've been granted elsewhere. Here's the part that trips people up: even when two different policy types are both denying you at once, AWS's own error message only ever names one of them, not both — so fixing the one it shows you can leave you exactly as locked out as before.

⚡ Quick Answer

Read the error message first → it names the policy type, e.g. "with an explicit deny in a service control policy"

Confirm it in CloudTrail → Event history, filter by the failed action, check the full errorMessage field

Six things can carry an explicit deny, and the message names only one of them even if several apply. See the full walkthrough for how to check the rest.

Jake called about a customer's laptop backup script that was failing overnight uploads to S3. The IAM user had s3:PutObject allowed, in writing, in a policy he'd checked twice. It still failed. "I gave it permission," he said. "AWS is lying to me."

It wasn't. AWS was telling the truth — just not the whole truth in one sentence. An explicit deny had been added somewhere else in the stack, and the identity policy Jake kept re-reading was never going to matter, because an explicit deny anywhere overrides an allow everywhere.

Explicit deny vs. implicit deny — they are not the same bug

Every AWS request starts denied by default. That's called an implicit deny: nothing said yes, so the answer is no. You clear an implicit deny by adding an Allow statement somewhere that covers the action and resource.

An explicit deny is different: a policy actually contains "Effect": "Deny" that matches your request. An explicit deny beats every Allow, in every policy, everywhere — including an Allow you add five minutes later in a completely different policy. You can't out-allow an explicit deny; you have to find it and remove or narrow it.

🙋‍♂️ Jake's Reality Check

"So if I just add another Allow policy that's bigger and covers everything, doesn't that win?"

No. Deny always wins over Allow, regardless of policy size, order, or how recently you attached it. The only fix for an explicit deny is finding and changing that specific Deny statement.

AWS's error message tells you which type of policy is responsible, using set phrasing: an explicit deny error contains the words "with an explicit deny in a <type> policy." An implicit deny error instead says "because no <type> policy allows the <action> action." Read the message carefully before you touch anything — those two phrasings send you down completely different fixes.

The six places an explicit deny can hide

AWS's enforcement code checks these, in this order, for a matching Deny statement. Finding one anywhere on this list stops the evaluation immediately with a final decision of Deny.

Policy type Who manages it Error message says
Identity-based policy You, or your account admin …an explicit deny in an identity-based policy
Resource-based policy Whoever owns the S3 bucket, SQS queue, KMS key, etc. …an explicit deny in a resource-based policy
Service Control Policy (SCP) Your organization's management account …an explicit deny in a service control policy
Resource Control Policy (RCP) Your organization's management account …an explicit deny in a resource control policy
Permissions boundary Whoever created your IAM user or role …an explicit deny in a permissions boundary
Session policy Whoever called AssumeRole / GetFederationToken …an explicit deny in a session policy

The catch: if more than one of these six is denying you at once, the error message names only one policy type. If two different types both deny, AWS shows you just one of them — you have to check the rest yourself. And if multiple policies of the same type deny you, the message doesn't say how many there are either.

✅ Why this is the one to check first

Whatever policy type the error names, check that one first — it's a real, confirmed deny, not a guess. But treat it as "at least this one," not "only this one," until you've ruled out the rest.

How to find which policy is actually denying you

The console error you see is usually truncated. Go straight to the full message.

  1. Open CloudTrail > Event history in the account where the request was made.
  2. Filter by Event name for the failing API action (for example, PutObject), and by the time the request failed.
  3. Open the matching event and read the full errorMessage field, not just the summary — the summary in the console list is often cut off before the policy type.
  4. Note the exact policy type named ("identity-based," "resource-based," "service control," "resource control," "permissions boundary," or "session"), the action, and the resource ARN.
  5. Go check that policy type first, using the sections below. If it's clean once fixed, re-run the request — if it still fails, work through the remaining five types on the table above, since the message won't tell you there's a second one.

If you don't see the event in CloudTrail at all, and you're going through a VPC endpoint, check the endpoint policy too — VPC endpoint policy denials sometimes don't log to CloudTrail the way IAM policy denials do, especially when the endpoint owner account differs from the account making the call.

Explicit deny in an identity-based policy

This is the one attached directly to your IAM user or role, or to a group your user belongs to. Open every policy attached to the principal (inline and managed, including group policies for a user) and search each for "Effect": "Deny". A typical shape looks like this: one statement allows a broad set of actions, a second statement explicitly denies a narrower, more dangerous subset — and the deny always wins for the overlap, even if someone later attaches a new policy that allows the exact same actions.

This pattern is common and intentional: an org lets an engineer manage most of IAM but explicitly denies anything ending in *Report, so credential reports and access reports stay locked down no matter what else gets granted later. If that's what you're looking at, the fix isn't to delete the deny — it's to confirm with whoever manages IAM in your account whether the restriction is deliberate before changing it.

Ethan's take

"Identity-based deny statements are the easiest to find and the easiest to misdiagnose," Ethan said. "People see the deny, delete it, and move on — without asking why someone put it there. Half the time it's guarding something you'd actually want guarded, like report generation or billing changes. Read it before you touch it."

Explicit deny in a resource-based policy

Resource-based policies live on the resource, not the identity — an S3 bucket policy, an SQS queue policy, a KMS key policy, a Lambda resource policy. Within the same account, most resources only need an explicit Allow in either the identity policy or the resource policy to grant access — but IAM role trust policies and KMS key policies are exceptions, and must explicitly allow the principal themselves.

A bucket policy denial is usually the fastest one to spot once you know where to look, because the whole statement is short. Something shaped like this, sitting on the bucket itself, will block PutObject for everyone except the one named role, no matter what the caller's own identity policy says:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPutExceptBackupRole",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::customer-backups/*",
      "Condition": {
        "StringNotLike": {
          "aws:PrincipalArn": "arn:aws:iam::111122223333:role/backup-writer"
        }
      }
    }
  ]
}

Notice the shape: "Principal": "*" means this applies to everyone, including the account root, unless the Condition block carves out an exception — which is exactly why bucket policy denies are worth reading condition-by-condition rather than skimming the Effect line and moving on.

⚠️ What this actually breaks

A deny statement with a broad Principal and a narrow condition, like the one above, locks out every role except the one it names — including the account admin, unless the admin's role happens to match the condition. That's rarely the intent when someone writes it; it's usually a leftover from a stricter lockdown that never got revisited.

How resource-based policies interact with an implicit deny elsewhere depends on the principal type. If the resource policy grants permission directly to an IAM user ARN, or to an assumed-role session ARN, that grant isn't limited by an implicit deny sitting in an identity policy, permissions boundary, or session policy — but if the resource policy names the IAM role ARN itself (rather than the session), an implicit deny in a permissions boundary or session policy can still apply. This is one of the more confusing corners of AWS access control, and it's worth re-reading twice if you're debugging cross-account access to a role rather than a user.

Explicit deny in a Service Control Policy (SCP)

SCPs are attached in AWS Organizations, at the organization root, an organizational unit, or a specific member account — and they don't grant anything on their own. An SCP is a ceiling: it caps what identity-based and resource-based policies in that account are even allowed to permit. No local policy, however permissive, can override an SCP's deny.

Two facts about SCPs catch people out constantly:

  • SCPs never apply to the management account. If you're testing "is this an SCP problem" from the org's management account, you'll never see the deny — because it literally can't affect you there. Test from an affected member account instead.
  • SCPs don't affect service-linked roles. Those roles exist so AWS services can integrate with each other, and Organizations explicitly excludes them from SCP restrictions.

You can't edit an SCP from a member account — only from the management account, or a delegated Organizations administrator. If CloudTrail names a service control policy, the fastest path is asking whoever manages your Organizations root to check the OU your account sits in for a matching Deny statement.

Explicit deny in a Resource Control Policy (RCP)

Resource Control Policies are the newer sibling of SCPs: instead of capping what your principals can do, an RCP caps what can be done to resources in the accounts it's attached to — regardless of which account the caller comes from. If you're chasing a deny on a resource owned by an account inside an organization that has RCPs enabled, and neither the resource policy nor your identity policy explains it, an RCP is worth ruling out next.

One default worth knowing: when RCPs are enabled for an organization, AWS automatically attaches a managed policy called RCPFullAWSAccess to every root, OU, and account, and that policy can't be detached — so there is always at least one Allow statement present at the RCP layer. A deny at this layer, then, means someone deliberately added a narrower RCP alongside it, not that RCPs were left unconfigured.

Explicit deny from a permissions boundary or a session policy

A permissions boundary is a managed policy attached to a specific IAM user or role that sets the maximum permissions that identity can ever have, no matter what its identity-based policies allow. It's an intersection, not a grant: your effective permissions are whatever your identity policy allows and the boundary allows, at the same time. A Deny statement in the boundary blocks the action even if your identity policy explicitly allows it.

Here's the shape this usually takes in practice: an organization lets team leads create IAM roles for their own projects, but every role they create is required to carry a standard boundary policy that denies iam:* actions, so a project role can never grant itself broader IAM permissions later, no matter how generous the identity policy attached to it becomes. If Jake's backup role could suddenly attach new policies to itself, that boundary is why it can't — the identity policy might allow iam:AttachRolePolicy, but the boundary denies it outright, and the boundary wins. When you're chasing a boundary-layer deny, check who set the boundary and whether removing it (rather than editing the identity policy) is even something you're authorized to do — boundaries are usually locked down precisely so the role they're attached to can't loosen them.

Session policies work the same intersecting way for temporary credentials — the policy document you pass when calling AssumeRole or GetFederationToken. If you don't pass one, there's no session policy to worry about. If you do, it also intersects with (never expands) whatever the underlying role or federated user was already allowed.

Testing it with IAM Policy Simulator and Access Analyzer

The IAM Policy Simulator lets you test identity-based policies, permissions boundaries, SCPs, and resource-based policies you provide, without sending a real request to any AWS service. When the result is an allow or an explicit deny, it also shows you which policy produced that outcome — which is exactly what you want when the CloudTrail message only named the policy type, not the statement.

  1. Open the Policy Simulator and choose the user, group, or role to test, or paste in policies that aren't attached yet.
  2. Pick the exact service and action from your failing request (match it to what CloudTrail logged, not a guess).
  3. Supply the resource ARN and any condition-key values the policies reference.
  4. Run the simulation and read the per-statement result — it flags exactly which statement in which policy produced the deny, for every policy type except SCPs, where it shows the allow/deny outcome but not the matched statement, for security reasons.
  5. If your account is in an organization, the simulator also folds in SCP impact on identity policies — useful for confirming an SCP-layer deny without needing management-account access yourself.
Situation Policy Simulator coverage
Resource control policies (RCPs)Not supported — check RCPs manually in Organizations
Session policiesNot simulated
Resource-based policy for an IAM roleNot supported; supported for IAM users on a limited set of services
Cross-account accessNot accurately simulated

For anything the simulator can't reach — RCPs, session policies, cross-account role access — IAM Access Analyzer is the better next stop. Its external and internal access findings show you which principals, inside or outside your account, actually have a path to a given resource; its unused access findings surface roles, keys, and permissions nobody is using, which is how a lot of stale deny statements get discovered in the first place; and its policy generation feature can draft a tighter replacement policy from a role's real CloudTrail activity, so you're not guessing at what the least-privilege version should allow.

When it's worth reaching for a dedicated tool

For a single failing request, CloudTrail plus the sections above is usually enough. It stops being enough once you're chasing this across dozens of accounts in an organization, or the same class of AccessDenied error is showing up repeatedly across a team. At that point, two options are worth knowing about:

  • IAM Access Analyzer, built into the account. No install, already covered above — reach for this first since it's free and already inside the console.
  • Access Undenied on AWS, an open-source CLI tool. It's a Python package (pip install access-undenied-aws) that reads a CloudTrail AccessDenied event file, works out the reason for the denial, and suggests a least-privilege fix. It's built specifically around the pain point of tracking down an explicit deny in an SCP, which the console alone doesn't make easy.

Don't reach for the CLI tool for a one-off "why did this request fail" question — exporting a CloudTrail event to a file and running a separate package is more setup than the problem needs. It earns its place when you're triaging AccessDenied errors at volume, or building the check into a pipeline, not when you're debugging the single event Jake just hit.

AWS CLI: reading and fixing explicit deny errors specifically

From the CLI, an AccessDeniedException or generic AccessDenied error usually prints the same policy-type phrasing as the console, just as plain text in your terminal: User: arn:aws:iam::111122223333:user/jake is not authorized to perform: s3:PutObject with an explicit deny in a service control policy. Copy that whole line before you do anything else — it has the exact ARN, action, and policy type you need for CloudTrail or the Policy Simulator.

If your CLI error is a bare "Access Denied" with none of that detail, you're usually looking at a service that doesn't return the extended message format, or a request signed with expired temporary credentials rather than a real permissions problem. Rule out expired credentials first — run aws sts get-caller-identity; if that fails too, the problem is authentication, not authorization, and no policy change will fix it.

Myths that waste your afternoon

"A security group is denying my API call." It isn't — security groups only support Allow rules, never Deny rules. Anything not explicitly allowed by a security group is implicitly blocked, but that's a network-layer connection failure (timeouts, connection refused), not an AccessDenied IAM error. If you want an actual network-layer Deny rule, that's a Network ACL, which supports both Allow and Deny and is evaluated separately from security groups entirely.

"AWS Config flagged something NON_COMPLIANT, so that must be my explicit deny." Different system. AWS Config rules like the one checking for admin-access statements just report a policy's compliance status against a rule you defined — they don't attach a Deny statement to anything by themselves. The exception is if your account has an automated remediation action wired to that rule; some remediation actions do modify or attach IAM policies in response to a NON_COMPLIANT finding, and that modification could be where your new Deny came from. Check the remediation history on the Config rule if the timing lines up.

🙋‍♂️ Jake's Reality Check

"I found the deny in my identity policy, deleted it, and it's still denying me. Is AWS broken?"

No — you found one of possibly several. Remember the error message only names one policy type even when more than one applies. Work through the remaining five from the table above before assuming anything is wrong on AWS's end.

Keeping this from happening again

Give every Deny statement a descriptive Sid the moment you write it — "DenyReportsExceptAudit," not "Statement1" — so the next person debugging an explicit deny (possibly you, in six months) doesn't have to reverse-engineer the intent from the JSON alone. If you manage SCPs or RCPs, keep a short changelog of what each deny statement is guarding against and why, since those are the two policy types least visible to the people they end up blocking.

Ethan's take

"The organizations that handle this well don't have fewer deny statements," Ethan said. "They just document every single one. The ones that handle it badly have a service-control policy from three years ago that nobody remembers writing, denying something nobody remembers why, and an entire engineering team afraid to touch it."

Frequently asked questions

What does "with an explicit deny" mean in an AWS error message?

It means a policy applicable to your request contains a matching "Effect": "Deny" statement, which overrides any Allow statements anywhere else. The message also names which of the six policy types contains it.

What's the difference between explicit deny and implicit deny?

An implicit deny happens when nothing allows the action — the default state for every AWS request. An explicit deny happens when a policy actively contains a Deny statement matching the request. Both produce AccessDenied, but the fix is different: implicit deny needs an Allow added; explicit deny needs the Deny statement found and changed.

How do I find which policy is denying me in AWS?

Read the full CloudTrail errorMessage for the failed event, note the policy type it names, check that policy type first, then work through the other five types if the request still fails after fixing the first one.

What does an explicit deny in an identity-based policy look like?

A statement inside a policy attached to your IAM user, role, or group with "Effect": "Deny" matching the action and resource you requested. It overrides any Allow in that same policy or any other identity-based, resource-based, or organizational policy.

In an IAM policy, does deny always win over allow?

Yes. If any applicable policy contains a matching Deny statement, the final decision is Deny, regardless of how many Allow statements exist elsewhere or how broad they are.

Can a Service Control Policy deny access even if my IAM policy allows it?

Yes. An SCP is a ceiling on what identity-based and resource-based policies in a member account are allowed to permit. A Deny in an SCP overrides any Allow in your identity policy, and no local policy can override the SCP.

Do Service Control Policies affect the management account?

No. SCPs don't affect users or roles in an organization's management account — only member accounts, including those designated as delegated administrators.

What is a Resource Control Policy (RCP) and how is it different from an SCP?

An SCP caps what a principal (a user or role) is allowed to do, wherever it goes. An RCP caps what can be done to resources in the accounts it's attached to, regardless of which account the calling principal belongs to. Both are enabled and managed through AWS Organizations.

Can a permissions boundary cause an explicit deny?

Yes. A permissions boundary sets the maximum permissions an IAM user or role can ever have. Effective permissions are the intersection of the identity policy and the boundary, and a Deny statement in the boundary blocks the action even if the identity policy explicitly allows it.

Can IAM Policy Simulator find an SCP-layer explicit deny?

Yes, if your account is in an organization, the simulator can show the impact of SCPs on identity-based policies. It reports whether the SCP layer allows or denies but does not show the matched SCP statement, and it does not support RCPs at all.

Why does my error message only mention one policy type when I have deny statements in two places?

AWS's evaluation logic stops at the first explicit deny it finds and returns that policy type in the message. If multiple policy types deny the same request, the message names only one of them and doesn't indicate more may apply.

Can a security group cause an "Access Denied" error?

No, not an IAM-style AccessDenied error. Security groups support only Allow rules, so unlisted traffic is implicitly blocked at the network layer, showing up as a connection timeout or refusal rather than an AccessDenied authorization error. Network ACLs are the AWS construct that supports explicit Deny rules.

Does an AWS Config NON_COMPLIANT finding cause an explicit deny?

Not by itself, a Config rule only reports a compliance status against a check you defined. If your account has an automated remediation action attached to that rule, and the remediation modifies or attaches an IAM policy, that action could be the source of a new Deny statement.

How do I read and fix an explicit deny error from the AWS CLI specifically?

The CLI prints the same detailed message the console does, including the policy type and the exact action and resource ARN. Copy that message, search the named policy type first, and rule out expired temporary credentials with aws sts get-caller-identity if the error is a bare "Access Denied" with no extended detail.

Can a resource-based policy override an explicit deny in an identity-based policy?

No. An explicit deny in an identity-based policy overrides an Allow anywhere else, including a resource-based policy. Resource-based policies can grant access despite an implicit deny elsewhere in some cases, but never override an explicit deny.

If one identity-based policy attached to me has a Deny, does an Allow in a different identity-based policy attached to the same user override it?

No. Identity-based policies are combined and evaluated together for a principal, and a Deny in any one of them overrides an Allow in any other, regardless of which policy was attached more recently.

Revision note. Written August 2026, covering the current six-layer IAM evaluation order including resource control policies (RCPs) alongside the older service control policy (SCP), identity-based, resource-based, permissions boundary, and session policy layers. This will need a fresh pass if AWS changes the evaluation order again or extends Policy Simulator's RCP coverage. If you've been staring at the same Deny statement for an hour, you're not missing something obvious — this genuinely is one of the more tangled corners of AWS, and it's fine to walk away and come back to it.

Related