AccessDenied (403 Forbidden)

AWS S3 Access Denied? Fix IAM Role Policy Conflicts in 6 Steps

S3 Access Denied with IAM roles usually means overlapping policies or a missing action. Check role trust, bucket policy, and explicit denies first.

Quick answer: Check for an explicit deny in any attached policy, verify the bucket policy allows the role's principal, and use the IAM Policy Simulator to test your exact role.

You set up an IAM role with what looks like full S3 access, attached it to your EC2 instance or Lambda, and boom—AccessDenied (403) when you try to list objects or upload. I've been there, staring at my role JSON wondering why s3:* isn't working. The reality is that S3 permissions are a three-way intersection test: the IAM role policy, the bucket policy, and any organizational policies all have to agree. Most of the time it's not the role you think it is—it's the bucket policy or a sneaky explicit deny.

Here's how to systematically untangle it. I've ordered these steps by how often they solve the problem.

Step 1: Check for Explicit Denies

Explicit denies beat every allow. If any policy attached to your role (or the bucket) has a Deny statement for the action you're trying, you're blocked. This could come from a service control policy (SCP) at the AWS organization level, a permissions boundary, or a session policy if you're using role chaining.

Look at your role's trust policy too—that's the policy that defines who can assume the role. If the trust policy doesn't include the service principal for your use case (like ec2.amazonaws.com), the role can't even be assumed, but you might get a different error. Still, check it.

Pro tip: In the IAM console, click your role and look for the Permissions boundaries section. If anything is listed there, it could be cutting off your access.

Step 2: Verify the Bucket Policy

A bucket policy can explicitly deny access to specific principals or even the whole world. If it's blocking your role's ARN, no IAM policy will save you. Here's a typical bucket policy that would block you:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "vpce-12345678"
        }
      }
    }
  ]
}

That policy denies everything except requests coming from a specific VPC endpoint. If your EC2 instance isn't using that endpoint, you're denied, period. The fix is either adjust the bucket policy or route traffic through the endpoint.

But more commonly, the bucket policy just doesn't mention your role at all. That's okay—if your IAM role has s3:*, it can access any bucket that doesn't have a deny. But if the bucket policy has an allow for another account or a specific user, it doesn't hurt you. The only thing that hurts is an explicit deny.

Step 3: Use IAM Policy Simulator

Don't guess. AWS gives you a tool that simulates exactly what your role can do. Go to the IAM console, find your role, and click Simulate under the Permissions tab. Select actions like s3:ListBucket and s3:PutObject, and run the simulation. It'll tell you if there's an explicit deny or an allow.

This saved me hours once when I had a permissions boundary that I forgot about. The simulator showed my role had Deny on s3:PutObject even though the policy looked fine.

Step 4: Check for Missing Actions

If you're trying to s3:ListBucket but your IAM policy only allows s3:GetObject, that's a problem. Here's a minimal policy that grants common S3 actions for a bucket called my-bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}

Note the Resource line. ListBucket needs the bucket ARN (arn:aws:s3:::my-bucket), while object actions need the object ARN (arn:aws:s3:::my-bucket/*). If you use a single resource with my-bucket/* for everything, ListBucket won't work because that ARN doesn't match the bucket itself. This is the number one mistake I see in forum posts.

Step 5: Look at IAM Role vs. User vs. Service Role

Is your role a service role (like for EC2 or Lambda) or are you trying to use it as a user? If you're assuming the role manually via the CLI, you might be missing the --role-arn parameter. I've done that more times than I'd like to admit—you think you're running as the role, but you're actually running as your user, which doesn't have S3 access. Check your current identity with:

aws sts get-caller-identity

That'll tell you exactly who you are. If it shows your user ARN instead of the role ARN, you're not actually using the role.

Step 6: Re-evaluate S3's 'ListAllMyBuckets'

If you're trying to list all buckets (s3:ListAllMyBuckets), note that this doesn't require a bucket resource—it's an API call at the account level. Your IAM policy needs to allow s3:ListAllMyBuckets with a Resource of *. But if you're trying to list objects in a specific bucket, that's s3:ListBucket. Mixing these up is common.

What If the Main Fixes Fail?

Try these alternatives before you rage-quit:

  • Use S3 Access Points – If your organization uses S3 Access Points, you can attach a separate policy to the access point that might override some bucket-level restrictions. It's a bit of a workaround, but it's legit.
  • Check S3 Block Public Access settings – Even if you have a public bucket for a website, if Block Public Access is on, you'll get AccessDenied. But for IAM roles, this rarely applies unless your bucket is set to public and you're relying on public access.
  • Enable CloudTrail for S3 – Turn on CloudTrail for the bucket temporarily to see the exact error reason. It'll show the request and the error code. Sometimes there's a condition key that's failing, like aws:SecureTransport requiring HTTPS.

One time, the issue was a Condition in the bucket policy that required aws:SourceIp to be a specific IP range. My Lambda function was running in a VPC with a different IP, so it got denied. CloudTrail showed the failing condition immediately.

Prevention Tip

Set up a regular IAM Access Analyzer review for your S3 buckets and roles. It'll flag policies that are too permissive (or too restrictive) before you get that 403 in production. Also, use managed policies like AmazonS3ReadOnlyAccess as a baseline, then attach a custom policy for the exact actions you need. That reduces the chance of missing actions.

And write down what you did. I keep a changelog for IAM policy changes. When something breaks later, I can see what changed and revert fast. Saves me from another four-hour debugging session.

Related Errors in Server & Cloud
0XC002002F Fix RPC_NT_BINDING_HAS_NO_AUTH (0XC002002F) Error 0XC00D2EE3 NS_E_BAD_REQUEST (0XC00D2EE3) — Why Windows Media Center throws this and how to fix it 0X00001707 Fix Cluster Property Data Type Mismatch (0x1707) Fast Connection refused: no route to host or corosync error Proxmox Cluster Node Shows Offline – Fix Step by Step

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.