AWS S3 403 Forbidden Error: Fix in 5 Minutes
Getting 403 Forbidden from S3 even though you're sure the bucket policy is right? Here is exactly how to diagnose IAM policy, bucket policy, ACL, block-public-access, and KMS key permission causes.
S3 403 errors are frustrating because there are five different places permission can be denied, and AWS gives you the same generic AccessDenied message regardless of which one it is. Here is how to find the real cause fast.
Step 1: Get the Real Reason From CloudTrail
# The console/CLI error is useless on its own — CloudTrail has the actual denial reason
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=GetObject \
--max-results 5 \
--query 'Events[*].CloudTrailEvent' --output text | jq .Look for "errorCode" and "errorMessage" in the event — this tells you whether it was IAM, bucket policy, or something else denying the request.
# Faster path if you have S3 server access logging or CloudTrail data events enabled:
aws s3api get-bucket-logging --bucket my-bucketCause 1: IAM Policy Doesn't Grant the Action
# Check what the calling identity can actually do
aws sts get-caller-identity
aws iam get-user-policy --user-name my-user --policy-name my-policy
# or for roles:
aws iam get-role-policy --role-name my-role --policy-name my-policy// Common mistake — policy grants s3:GetObject but not s3:ListBucket,
// so listing works in console but GetObject fails, or vice versa
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}Fix — you usually need both the bucket-level and object-level permissions:
{
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::my-bucket"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/*"
}Note the ARN difference — arn:aws:s3:::my-bucket (no /*) for bucket-level actions, arn:aws:s3:::my-bucket/* for object-level actions. Mixing these up is the single most common cause of this error.
Cause 2: Bucket Policy Explicitly Denies
aws s3api get-bucket-policy --bucket my-bucket --query Policy --output text | jq .An explicit Deny in the bucket policy always wins over an Allow in an IAM policy — this is the rule people forget.
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:SourceVpce": "vpce-0abc123"
}
}
}If your bucket has a VPC-endpoint restriction like this and you're testing from your laptop (not through the VPC endpoint), you will always get 403 regardless of your IAM permissions. Check every Condition block carefully.
Cause 3: Block Public Access Settings
aws s3api get-public-access-block --bucket my-bucket{
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
}
}If BlockPublicPolicy is true, S3 will reject bucket policies that grant public access even if you write one — and RestrictPublicBuckets blocks public access even through an otherwise-valid policy. This is usually correct for security but breaks intentionally-public buckets (like static website hosting) if left on by default.
Fix — only if the bucket is meant to be public:
aws s3api put-public-access-block --bucket my-bucket \
--public-access-block-configuration \
BlockPublicAcls=false,IgnorePublicAcls=false,BlockPublicPolicy=false,RestrictPublicBuckets=falseCause 4: Bucket Owner vs Object Owner Mismatch (Cross-Account)
# When another account uploaded objects to your bucket without
# specifying bucket-owner-full-control, you don't own the objects
aws s3api get-object-acl --bucket my-bucket --key uploaded-file.txtFix — require the uploading account to set the ACL, or enable Bucket Owner Enforced (recommended for 2026):
aws s3api put-bucket-ownership-controls --bucket my-bucket \
--ownership-controls Rules=[{ObjectOwnership=BucketOwnerEnforced}]BucketOwnerEnforced disables ACLs entirely and makes the bucket owner the object owner for everything uploaded — this eliminates the whole class of cross-account ownership 403s.
Cause 5: SSE-KMS Key Permissions
# If the bucket uses a customer-managed KMS key for encryption,
# s3:GetObject succeeding requires kms:Decrypt on that key too
aws kms get-key-policy --key-id alias/my-bucket-key --policy-name default{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::123456789012:role/my-app-role"},
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": "*"
}Missing kms:Decrypt on the key policy is a frequent cause when everything on the S3 side looks correct — the IAM role has s3:GetObject, the bucket policy allows it, but the KMS key policy never granted decrypt access to that role.
Diagnostic Checklist
aws sts get-caller-identity # who are you actually?
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/my-role \
--action-names s3:GetObject \
--resource-arns arn:aws:s3:::my-bucket/my-key # simulates the exact call
aws s3api get-bucket-policy --bucket my-bucket
aws s3api get-public-access-block --bucket my-bucket
aws kms get-key-policy --key-id alias/my-key --policy-name defaultiam simulate-principal-policy is the fastest way to cut through all five causes at once — it tells you exactly which policy is denying the request.
More AWS troubleshooting? Read our AWS RDS connection timeout fix and AWS ECR push denied authentication fix.
Today I Fixed
Short real fixes from production — posted daily
Stay ahead of the curve
Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.
Related Articles
AWS IAM AssumeRole Access Denied: Fix in 5 Minutes
sts:AssumeRole failing with AccessDenied even though the role exists and the policy looks right? Here is exactly how to diagnose trust policy, permission boundary, session policy, and external ID causes.
AWS IAM Permission Denied Errors — How to Fix Every Variant (2026)
Getting 'Access Denied' or 'is not authorized to perform' errors in AWS? Here's how to diagnose and fix every IAM permission issue — EC2, EKS, Lambda, S3, and CLI.
AWS IRSA Permission Denied in Kubernetes — Fix
Your Kubernetes pod can't access AWS services even though IRSA is configured. Here's every reason IRSA fails and exactly how to debug and fix each one.