What's Actually Happening
S3 returns 'Access Denied' (403 Forbidden) when you attempt to upload an object, even though you believe you have the correct permissions. The error can come from multiple layers: IAM policies, bucket policies, ACLs, encryption settings, or account-level blocks.
The Error You'll See
Using AWS CLI:
$ aws s3 cp file.txt s3://my-bucket/
upload failed: ./file.txt to s3://my-bucket/file.txt An error occurred (AccessDenied) when calling the PutObject operation: Access DeniedUsing SDK:
botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the PutObject operation: Access DeniedWhy This Happens
- 1.Missing IAM permissions - The IAM user/role lacks
s3:PutObjectpermission - 2.Bucket policy blocking access - Explicit deny in bucket policy
- 3.KMS key policy - If bucket uses SSE-KMS, you need
kms:GenerateDataKeypermission - 4.Object ownership - Bucket has ACLs disabled or different owner
- 5.Public access block - Account-level block on public buckets being applied incorrectly
- 6.VPC endpoint policy - Restrictive policy on S3 VPC endpoint
- 7.Object Lock - Bucket has Object Lock enabled and you're overwriting a locked object
Step 1: Verify IAM Permissions
Check the effective permissions for your identity:
```bash # Simulate the PutObject action aws iam simulate-principal-policy \ --policy-source-arn arn:aws:iam::123456789012:user/myuser \ --action-names s3:PutObject \ --resource-arns arn:aws:s3:::my-bucket/*
# Check attached policies aws iam list-attached-user-policies --user-name myuser aws iam list-user-policies --user-name myuser ```
Required IAM permission:
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}Step 2: Check Bucket Policy
aws s3api get-bucket-policy --bucket my-bucket --output jsonLook for explicit denies or conditions that might block your upload:
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalArn": "arn:aws:iam::123456789012:role/AllowedRole"
}
}
}An explicit deny always overrides any allow.
Step 3: Check KMS Key Permissions
If the bucket uses SSE-KMS encryption:
```bash # Check bucket encryption aws s3api get-bucket-encryption --bucket my-bucket
# Check KMS key policy aws kms get-key-policy --key-id alias/my-key --policy-name default ```
You need both:
{
"Effect": "Allow",
"Action": [
"kms:GenerateDataKey",
"kms:Encrypt"
],
"Resource": "arn:aws:kms:region:account:key/key-id"
}Step 4: Check Object Ownership
aws s3api get-bucket-ownership-controls --bucket my-bucketIf ObjectOwnership is set to BucketOwnerEnforced, ACLs are disabled and any ACL in your upload request will cause a failure.
Step 5: Test with Minimal Request
Isolate the issue by testing with a minimal upload:
aws s3api put-object --bucket my-bucket --key test.txt --body test.txtIf this works, the issue may be with additional headers like:
- x-amz-acl (ACL header)
- x-amz-server-side-encryption (encryption header)
- x-amz-storage-class (storage class)
Step 6: Check VPC Endpoint Policy
If accessing S3 via VPC endpoint:
aws ec2 describe-vpc-endpoints --filters Name=service-name,Values=com.amazonaws.region.s3Check the endpoint policy for restrictions on PutObject.
Step 7: Enable S3 Server Access Logging
For persistent issues, enable access logging to see the actual denial reason:
aws s3api put-bucket-logging --bucket my-bucket \
--bucket-logging-status '{
"LoggingEnabled": {
"TargetBucket": "log-bucket",
"TargetPrefix": "s3-access-logs/"
}
}'Check logs for the specific denial reason.
Verify the Fix
aws s3 cp test.txt s3://my-bucket/test.txt --debugLook for the HTTP response code in debug output. A successful upload returns 200.
Related Issues
- [Fix AWS S3 Bucket Policy Too Restrictive](/articles/fix-aws-s3-bucket-policy-restrictive)
- [Fix AWS S3 404 NoSuchBucket Error](/articles/fix-aws-s3-nosuchbucket-error)
- [Fix AWS KMS Access Denied](/articles/fix-aws-kms-access-denied)