AWS S3 Access Denied (403 Forbidden): Complete Troubleshooting Guide
Fix AWS S3 Access Denied and 403 Forbidden errors fast. Diagnose IAM policies, bucket policies, KMS keys, Block Public Access, throttling, and timeouts step by
- AWS S3 'Access Denied' (HTTP 403) is caused by a conflict in one of five policy layers — IAM identity policies, S3 bucket policies, S3 ACLs, Block Public Access settings, or KMS key policies. A single explicit Deny in any layer overrides all Allow statements everywhere else.
- KMS encryption mismatches are the most frequently overlooked root cause: even perfect S3 IAM permissions will produce Access Denied if the calling role lacks kms:Decrypt or kms:GenerateDataKey on the bucket's CMK.
- For S3 throttling (HTTP 503 SlowDown) and timeouts, use AWS SDK adaptive retry mode, randomize object key prefixes to spread load across partitions, and create an S3 VPC Gateway Endpoint to eliminate NAT bottlenecks.
| Method | When to Use | Time | Risk |
|---|---|---|---|
| Add IAM policy statement | Caller's IAM role lacks s3:GetObject, s3:PutObject, or s3:ListBucket | 2–5 min | Low — additive change only |
| Edit S3 bucket policy | Bucket policy has an explicit Deny or missing Allow for the principal | 5–10 min | Medium — syntax errors lock out all principals |
| Disable Block Public Access | Public bucket or presigned URL returns 403 due to BPA override | 1–2 min | High — exposes bucket publicly if misconfigured |
| Grant KMS key access | Bucket uses SSE-KMS and caller lacks kms:Decrypt or kms:GenerateDataKey | 5 min | Low — additive key policy or IAM change |
| Cross-account trust + resource policy | Different AWS account needs access; both sides must grant permission | 10–15 min | Medium — requires coordinated changes in two accounts |
| Exponential backoff + prefix sharding | 503 SlowDown errors on high-TPS workloads concentrated on one prefix | 30–60 min (code change) | Low — improves reliability with no data risk |
Understanding AWS S3 Access Denied Errors
When Amazon S3 returns an Access Denied error (HTTP 403 Forbidden) or a Permission Denied message, the request was authenticated but not authorized. S3 evaluates authorization across five independent policy layers: IAM identity policies, S3 bucket policies, S3 Access Control Lists (ACLs), S3 Block Public Access (BPA) settings, and AWS KMS key policies. A single explicit Deny in any layer vetoes all Allow statements across every other layer.
The exact error strings you will encounter:
An error occurred (AccessDenied) when calling the GetObject operation: Access Denied403 ForbiddenAccess Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied)User: arn:aws:iam::123456789012:user/alice is not authorized to perform: s3:GetObject on resource: arn:aws:s3:::my-bucket/file.txt503 SlowDown: Please reduce your request rate.RequestTimeout/ReadTimeoutError/ConnectTimeoutError
Identifying which layer rejected the request is the most important first step.
Step 1: Diagnose the Root Cause
1a. Capture the Full Error
The AWS CLI --debug flag prints the full HTTP response including headers and error body:
aws s3api get-object --bucket my-bucket --key file.txt /dev/null --debug 2>&1 \
| grep -E '(AccessDenied|403|Error|errorCode)'
1b. Use the IAM Policy Simulator
The IAM Policy Simulator evaluates all policies in effect for a principal and reports which policy is responsible for a denial:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/my-app-role \
--action-names s3:GetObject s3:ListBucket \
--resource-arns \
arn:aws:s3:::my-bucket \
"arn:aws:s3:::my-bucket/*"
Look for "EvalDecision": "explicitDeny" — this confirms a hard deny is in play and shows the MatchedStatements field that identifies the exact policy document.
1c. Inspect CloudTrail Logs
CloudTrail records every S3 API call including the authorization decision, the IAM principal, and the source IP:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue=my-bucket \
--start-time 2024-01-15T00:00:00Z \
--query 'Events[?contains(CloudTrailEvent,`AccessDenied`)].CloudTrailEvent' \
--output text | python3 -m json.tool
The errorCode and errorMessage fields in each CloudTrail entry identify the denying policy type.
1d. Check Block Public Access
For objects accessed via presigned URLs or public bucket policies, Block Public Access settings may silently override all other permissions:
aws s3api get-public-access-block --bucket my-bucket
All four BPA settings default to true for buckets created after April 2023.
Step 2: Fix IAM Identity Permissions
The most frequent cause of S3 Access Denied is a missing IAM permission on the calling role or user. Attach this inline or managed policy to the principal:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3BucketList",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Sid": "AllowS3ObjectAccess",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
Critical distinction: s3:ListBucket must target the bucket ARN (arn:aws:s3:::my-bucket), while object-level actions must target the object pattern (arn:aws:s3:::my-bucket/*). Confusing these is the most common IAM misconfiguration.
Step 3: Fix the S3 Bucket Policy
A bucket policy explicit Deny overrides any IAM Allow. Inspect the existing policy:
aws s3api get-bucket-policy \
--bucket my-bucket \
--query Policy \
--output text | python3 -m json.tool
Search for Deny statements that may be unintentionally broad. A safe cross-account access grant looks like:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountRead",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:role/external-role"
},
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
Always test bucket policy changes with the IAM Policy Simulator before saving, since a malformed policy can lock out all principals including the bucket owner.
Step 4: Resolve KMS Encryption Issues
If the bucket uses SSE-KMS, the calling principal needs both S3 permissions and KMS permissions. Missing KMS access surfaces identically to a missing S3 permission — the error message says Access Denied on GetObject or PutObject.
Required KMS permissions:
kms:Decrypt— for reading encrypted objectskms:GenerateDataKey— for writing new objectskms:DescribeKey— for key metadata operations
# Identify the KMS key used by the bucket
aws s3api get-bucket-encryption --bucket my-bucket
# Simulate KMS access for the calling role
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/my-app-role \
--action-names kms:Decrypt kms:GenerateDataKey \
--resource-arns arn:aws:kms:us-east-1:123456789012:key/mrk-abc123
If the KMS key is in a different account, you must also update the KMS key policy to allow the external principal — IAM policies alone are insufficient for cross-account KMS usage.
Step 5: Fix Cross-Account Access
For cross-account S3 access, both sides must independently grant permission. Neither side alone is sufficient:
- Bucket account: Add the external role as a principal in the S3 bucket policy (see Step 3).
- External account: Attach an IAM policy to the calling role explicitly allowing S3 actions on the bucket ARN.
Without both sides configured, S3 returns Access Denied regardless of how permissive one side is.
Step 6: Handle S3 Rate Limiting (503 SlowDown)
AWS S3 supports up to 5,500 GET/HEAD requests/sec and 3,500 PUT/COPY/POST/DELETE requests/sec per key prefix partition. When requests concentrate on one prefix, S3 returns:
503 SlowDown: Please reduce your request rate.
S3 automatically re-partitions after sustained load (~30 minutes), but you should take proactive steps:
1. Enable adaptive retry in the AWS SDK:
import boto3
from botocore.config import Config
config = Config(retries={"max_attempts": 10, "mode": "adaptive"})
s3 = boto3.client("s3", config=config)
2. Randomize key prefixes to spread load across partitions:
- Before:
logs/2024-01-15/app-001.log - After:
a3f7/logs/2024-01-15/app-001.log(prepend hash of key)
This simple change can increase effective throughput by orders of magnitude for high-TPS workloads.
Step 7: Resolve S3 Timeouts
S3 timeouts are typically caused by large objects on slow connections, VPC NAT gateway saturation, or missing S3 VPC endpoints. Traffic routing from EC2 through a NAT gateway to S3 is both slow and costly.
Create a free S3 VPC Gateway Endpoint to route traffic directly:
aws ec2 create-vpc-endpoint \
--vpc-id vpc-12345678 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-12345678
For SDK timeout tuning with boto3:
config = Config(
connect_timeout=10,
read_timeout=60,
retries={"max_attempts": 5, "mode": "adaptive"}
)
For large file transfers (>100 MB), use multipart upload or aws s3 cp which handles multipart automatically and supports resumable transfers.
Frequently Asked Questions
#!/usr/bin/env bash
# S3 Access Denied Diagnostic Script
# Usage: ./s3-diagnose.sh <bucket-name> <object-key> <iam-role-arn>
BUCKET="${1:-my-bucket}"
KEY="${2:-test/file.txt}"
ROLE_ARN="${3:-arn:aws:iam::123456789012:role/my-app-role}"
echo "=== 1. Caller Identity ==="
aws sts get-caller-identity
echo ""
echo "=== 2. Direct S3 Access Test ==="
aws s3api get-object \
--bucket "$BUCKET" \
--key "$KEY" \
/dev/null 2>&1 || true
echo ""
echo "=== 3. Block Public Access Settings ==="
aws s3api get-public-access-block --bucket "$BUCKET" 2>&1
echo ""
echo "=== 4. Bucket Policy ==="
aws s3api get-bucket-policy \
--bucket "$BUCKET" \
--query Policy \
--output text 2>&1 \
| python3 -m json.tool 2>/dev/null \
|| echo "[No bucket policy or access denied to read policy]"
echo ""
echo "=== 5. Bucket Encryption (SSE-KMS?) ==="
aws s3api get-bucket-encryption --bucket "$BUCKET" 2>&1
echo ""
echo "=== 6. IAM Policy Simulator ==="
aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names s3:GetObject s3:ListBucket s3:PutObject s3:DeleteObject \
--resource-arns \
"arn:aws:s3:::${BUCKET}" \
"arn:aws:s3:::${BUCKET}/${KEY}" \
--query 'EvaluationResults[*].{Action:EvalActionName,Decision:EvalDecision}' \
--output table 2>&1
echo ""
echo "=== 7. Check for KMS Key (if SSE-KMS) ==="
KMS_KEY=$(aws s3api get-bucket-encryption --bucket "$BUCKET" \
--query 'ServerSideEncryptionConfiguration.Rules[0].ApplyServerSideEncryptionByDefault.KMSMasterKeyID' \
--output text 2>/dev/null)
if [ -n "$KMS_KEY" ] && [ "$KMS_KEY" != "None" ]; then
echo "Bucket KMS key: $KMS_KEY"
aws iam simulate-principal-policy \
--policy-source-arn "$ROLE_ARN" \
--action-names kms:Decrypt kms:GenerateDataKey kms:DescribeKey \
--resource-arns "$KMS_KEY" \
--query 'EvaluationResults[*].{Action:EvalActionName,Decision:EvalDecision}' \
--output table 2>&1
else
echo "Bucket does not use SSE-KMS or encryption info unavailable."
fi
echo ""
echo "=== 8. Recent Access Denied Events (CloudTrail, last 2h) ==="
START=$(date -u -d '2 hours ago' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null \
|| date -u -v-2H '+%Y-%m-%dT%H:%M:%SZ')
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=ResourceName,AttributeValue="$BUCKET" \
--start-time "$START" \
--query 'Events[?contains(CloudTrailEvent,`AccessDenied`)].{Time:EventTime,User:Username,Event:EventName}' \
--output table 2>&1 | head -50
echo ""
echo "=== Diagnosis complete. Look for explicitDeny in simulator and Deny statements in bucket policy. ==="Error Medic Editorial
The Error Medic Editorial team comprises senior DevOps and SRE engineers with collective experience managing AWS infrastructure at scale across financial services, SaaS, and media industries. Our troubleshooting guides are built from real incident postmortems and validated against current AWS documentation and service limits.
Sources
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/troubleshooting.html
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-overview.html
- https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_access-denied.html
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/request-rate-perf-considerations.html
- https://repost.aws/knowledge-center/s3-access-denied-error
- https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html