# Fix AWS S3 Bucket Permission Denied

The dreaded "Access Denied" error when accessing S3 is one of the most common AWS issues. The frustrating part is that S3 doesn't always tell you *which* permission is missing—it just denies access. The problem could be in your IAM policy, the bucket policy, ACLs, or even the bucket's public access settings.

Let me walk you through a systematic approach to track down exactly what's blocking your access.

Understanding S3 Permission Layers

S3 has multiple layers of permissions that all need to align:

  1. 1.IAM policies attached to users, groups, or roles
  2. 2.Bucket policies attached to the bucket itself
  3. 3.Access Control Lists (ACLs) on the bucket or objects
  4. 4.Public Access Block settings that can override other permissions
  5. 5.Object ownership settings

If any one of these layers denies access, the request fails. The default is deny—everything must explicitly allow.

Diagnosis Commands

Start by identifying who you're authenticated as:

bash
aws sts get-caller-identity

This tells you the account, user ID, and ARN of the caller. Make sure this matches what you expect.

Now try to list the bucket contents:

bash
aws s3 ls s3://my-bucket-name/

If you get "Access Denied," try with the --debug flag to see more details:

bash
aws s3 ls s3://my-bucket-name/ --debug 2>&1 | grep -i "denied\|forbidden\|403"

Check if you can read a specific object:

bash
aws s3api head-object \
  --bucket my-bucket-name \
  --key path/to/object.txt

If the bucket exists but you can't access it, check your effective permissions using the policy simulator:

bash
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:user/my-user \
  --action-names s3:GetObject,s3:ListBucket \
  --resource-arns arn:aws:s3:::my-bucket-name,arn:aws:s3:::my-bucket-name/*

Check the bucket's public access block settings:

bash
aws s3api get-public-access-block \
  --bucket my-bucket-name

If this command fails with "NoSuchPublicAccessBlockConfiguration," no block is applied. Otherwise, you'll see which restrictions are in place.

Examine the bucket policy:

bash
aws s3api get-bucket-policy \
  --bucket my-bucket-name \
  --query Policy \
  --output text | jq .

If you can't read the bucket policy due to permission issues, check if you have s3:GetBucketPolicy permission in your IAM policy.

Check the bucket ACL:

bash
aws s3api get-bucket-acl \
  --bucket my-bucket-name

And check object-level ACLs:

bash
aws s3api get-object-acl \
  --bucket my-bucket-name \
  --key path/to/object.txt

Common Causes and Solutions

Missing IAM Policy Permissions

The most common issue is simply not having the right permissions in your IAM policy. Add the required actions:

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

Apply the policy:

bash
aws iam put-user-policy \
  --user-name my-user \
  --policy-name S3AccessPolicy \
  --policy-document file://s3-policy.json

Remember that s3:ListBucket requires the bucket ARN (without /*), while s3:GetObject and s3:PutObject require the object ARN (with /*).

Restrictive Bucket Policy

The bucket policy might be explicitly denying your access. Look for Deny statements:

bash
aws s3api get-bucket-policy \
  --bucket my-bucket-name \
  --query Policy \
  --output text | jq '.Statement[] | select(.Effect=="Deny")'
  1. 1.If you find a restrictive policy, you'll need to either:
  2. 2.Have the bucket owner modify the policy
  3. 3.Access the bucket from a different account or role that's allowed
  4. 4.Use a bucket owner account with full access

Here's an example of a bucket policy that grants access to a specific user:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowUserAccess",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789012:user/my-user"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket-name",
        "arn:aws:s3:::my-bucket-name/*"
      ]
    }
  ]
}

Apply it:

bash
aws s3api put-bucket-policy \
  --bucket my-bucket-name \
  --policy file://bucket-policy.json

Cross-Account Access Issues

If you're accessing a bucket in another AWS account, both sides need to grant permission:

  1. 1.The bucket policy must allow your account
  2. 2.Your IAM policy must allow access to the bucket

Bucket policy in Account B (bucket owner):

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNT-A-ID:root"
      },
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::bucket-in-account-b",
        "arn:aws:s3:::bucket-in-account-b/*"
      ]
    }
  ]
}

IAM policy in Account A (your account):

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::bucket-in-account-b",
        "arn:aws:s3:::bucket-in-account-b/*"
      ]
    }
  ]
}

Public Access Block Interference

If you're trying to make a bucket or object public and getting access denied, check the public access block settings:

bash
aws s3api get-public-access-block \
  --bucket my-bucket-name

If any of these are enabled, they prevent public access: - BlockPublicAcls - IgnorePublicAcls - BlockPublicPolicy - RestrictPublicBuckets

Disable them if you truly need public access:

bash
aws s3api put-public-access-block \
  --bucket my-bucket-name \
  --public-access-block-configuration "BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=false"

Object Ownership Issues

With S3 Object Ownership, the bucket owner automatically owns objects written by other accounts. Check the ownership setting:

bash
aws s3api get-bucket-ownership-controls \
  --bucket my-bucket-name

If set to BucketOwnerEnforced, ACLs are disabled and the bucket owner owns all objects. This is generally the recommended setting but can cause issues with legacy cross-account setups.

Wrong Region

Sometimes you get access denied when the bucket is in a different region than your client is configured for. Always specify the region:

bash
aws s3 ls s3://my-bucket-name/ --region us-west-2

Verification Steps

After making changes, verify your access works:

```bash # Test listing aws s3 ls s3://my-bucket-name/

# Test reading aws s3api get-object \ --bucket my-bucket-name \ --key test-object.txt \ /dev/null

# Test writing (if needed) echo "test" | aws s3 cp - s3://my-bucket-name/test-write.txt

# Clean up test file aws s3 rm s3://my-bucket-name/test-write.txt ```

For ongoing access issues, enable S3 server access logging to see exactly what's being denied:

bash
aws s3api put-bucket-logging \
  --bucket my-bucket-name \
  --bucket-logging-status '{
    "LoggingEnabled": {
      "TargetBucket": "my-logs-bucket",
      "TargetPrefix": "s3-access-logs/"
    }
  }'

Then analyze the logs for patterns in denied requests.