AWS S3 403 Forbidden – Access Denied
Encountering AWS S3 403 Forbidden – Access Denied means your request to an S3 bucket is blocked by permissions; this guide explains how to diagnose and fix it.
As a Platform Reliability Engineer, I've spent countless hours debugging AWS S3 permission issues. The "403 Forbidden – Access Denied" error is one of the most common, yet often misunderstood, problems developers and operations teams face. It signifies that while your request successfully reached S3, the principal (user or role) making the request lacks the necessary authorization to perform the requested action on the specified resource. This isn't an authentication failure (your credentials are valid), but a precise authorization wall.
What This Error Means
When you see "AWS S3 403 Forbidden – Access Denied," it means your AWS credentials were successfully authenticated, but the identity associated with those credentials is not authorized to perform the specific S3 action you requested (e.g., s3:GetObject, s3:PutObject, s3:DeleteObject, s3:ListBucket) on the target S3 bucket or object. It's a clear signal that a permission boundary is being hit somewhere in the complex web of AWS authorization.
Unlike a 401 Unauthorized error, which would imply invalid credentials, 403 Forbidden indicates that the request itself is disallowed based on existing policies. This distinction is crucial for effective troubleshooting.
Why It Happens
At its core, this error happens because AWS's least privilege security model is working. S3 resources are highly protected, and access is explicitly granted, not implicitly assumed. When you make a request via the AWS CLI or an SDK, S3 evaluates a hierarchy of permissions to determine if the action should be allowed. If any policy in this chain denies the action, or if no policy explicitly allows it, you're met with the 403. In my experience, it's rarely a bug in AWS; it's almost always a misconfiguration in how permissions are defined.
Common Causes
Troubleshooting a 403 Access Denied error on S3 often feels like peeling an onion, layer by layer. Here are the most common culprits I've encountered:
- Incorrect IAM Policies (User/Role): The IAM policy attached to your user or role simply doesn't grant the necessary S3 permissions. This is often the first place to look.
- S3 Bucket Policies: A policy directly attached to the S3 bucket might explicitly deny your action, or it might not grant it when the IAM policy doesn't. Bucket policies can override or restrict IAM user policies.
- S3 Access Control Lists (ACLs): While less common for fine-grained access, ACLs still control object ownership and basic read/write permissions at the bucket and object level. They're particularly relevant for cross-account object uploads.
- S3 Block Public Access (BPA) Settings: These account-level and bucket-level settings are designed to prevent public access to S3 buckets. If they're enabled (which is recommended), they can block public
GetObjectorListBucketrequests, even if a bucket policy explicitly allows it. - Object Ownership: When objects are uploaded to a bucket by a different AWS account, the default object ownership setting can prevent the bucket owner from accessing those objects without explicit ACLs or a
bucket-owner-full-controlsetting during upload. - Missing MFA Delete: If versioning is enabled on a bucket and MFA Delete is configured, you'll get a 403 when trying to delete objects without providing an MFA token.
- VPC Endpoint Policy: If you're accessing S3 through a VPC Endpoint, the endpoint policy itself might be restricting access, even if your IAM and S3 bucket policies are correct.
- Cross-Account Access Misconfiguration: When one AWS account needs to access an S3 bucket in another account, both the IAM policy of the requesting principal and the bucket policy of the target bucket must explicitly permit the access.
Step-by-Step Fix
Here's my systematic approach to diagnosing and fixing S3 403 errors, typically starting from the client-side and moving towards the S3 resource.
-
Identify the Requesting Principal:
First, confirm which IAM user or role is making the request. This is crucial because all policy evaluations hinge on this identity.
bash aws sts get-caller-identity
This command will return details likeUserId,Account, andArn. Note theArn. -
Check S3 Block Public Access (BPA) Settings:
Even if you're not trying to make the bucket public, BPA can sometimes interfere if you're hitting specific edge cases or legacy configurations.- Account Level: Navigate to S3 in the AWS console, then "Block Public Access settings for this account." Ensure these aren't inadvertently blocking your desired internal access patterns.
- Bucket Level: Select your bucket, go to "Permissions," and check "Block Public Access settings." While usually focused on public access, I've seen them interact with complex permission setups.
-
Evaluate IAM User/Role Policy:
This is the policy directly attached to theget-caller-identityprincipal.- Go to IAM in the AWS Console -> Users or Roles -> [Your User/Role] -> Permissions.
- Review the attached policies. Look for
s3:actions. Ensure the desired action (e.g.,s3:GetObjectfor download,s3:PutObjectfor upload,s3:ListBucketforls) is allowed on the target bucket or objects. - Pay close attention to
ResourceARNs. Are they correctly specified for the bucket or objects?arn:aws:s3:::your-bucket-name/*for objects andarn:aws:s3:::your-bucket-namefor bucket-level actions. - Pro Tip: Use the IAM Policy Simulator. Go to IAM -> Policy Simulator, select your user/role, choose S3 service, and simulate the action on the target resource. This is an invaluable tool for understanding policy evaluation.
-
Examine S3 Bucket Policy:
Bucket policies are attached directly to the S3 bucket and apply to all principals interacting with it. They can grant or deny permissions regardless of IAM policies.- Go to S3 in the AWS Console -> Buckets -> [Your Bucket] -> Permissions -> Bucket policy.
- Review the JSON policy. Look for
Denystatements that might be blocking your action. - Crucially, if your IAM policy allows the action, but the bucket policy doesn't explicitly allow it (especially for cross-account access), it will be denied.
- If you suspect an issue, temporarily comment out sections or add an explicit
Allowstatement for your principal, then test and revert.
```bash
Get the current bucket policy
aws s3api get-bucket-policy --bucket your-bucket-name --query Policy --output text
Put a new policy (after editing the JSON locally)
aws s3api put-bucket-policy --bucket your-bucket-name --policy file://policy.json
``` -
Review S3 Access Control Lists (ACLs):
While bucket policies are generally preferred, ACLs still govern object ownership and can sometimes be a factor, especially with cross-account object uploads where the uploader owns the object, not the bucket owner.- Go to S3 -> Buckets -> [Your Bucket] -> Permissions -> Access Control List (ACLs).
- Check for granular permissions at the bucket level.
- If you're accessing an object uploaded by another account, check the object's ACL (select object -> Permissions). The uploading account might not have granted
BUCKET_OWNER_FULL_CONTROLor similar. I've often seen this in production when different teams upload data without a unified S3 strategy.
-
Object Ownership:
Related to ACLs, the "Object Ownership" setting on the bucket dictates who owns newly uploaded objects.- Go to S3 -> Buckets -> [Your Bucket] -> Permissions -> Object Ownership.
Bucket owner enforcedis the recommended setting and simplifies permissions by disabling ACLs. IfObject writeris enabled, the uploader owns the object, which can lead to 403s if the bucket owner then tries to access it without specific ACLs.
-
MFA Delete:
If you're trying to delete an object from a versioned bucket and get a 403, check if MFA Delete is enabled.- Go to S3 -> Buckets -> [Your Bucket] -> Properties -> Versioning.
- If MFA Delete is enabled, you'll need to provide MFA credentials with your delete command.
-
VPC Endpoint Policy:
If your application is inside a VPC and accessing S3 through a VPC Endpoint, the endpoint policy acts as an additional layer of access control.- Navigate to VPC -> Endpoints -> [Your S3 Endpoint] -> Policy.
- Ensure the endpoint policy allows traffic from your VPC to the S3 bucket.
-
Check CloudTrail Logs:
The ultimate source of truth. CloudTrail records all API calls made to AWS services.- Go to CloudTrail -> Event history.
- Filter by "Event name" (e.g.,
GetObject,PutObject,ListBuckets) and "Resource name" (your bucket name). - Look for events with
errorCode: "AccessDenied". TheerrorMessageandresponseElements.x-amz-error-codeoften provide specific details about which policy denied the request. This is particularly useful in complex scenarios where multiple policies are at play.
Code Examples
Here are some concise, copy-paste ready examples of policies and CLI commands to help in troubleshooting and configuration.
IAM Policy for S3 Read-Only Access:
This policy allows an IAM user/role to list a specific bucket and read objects within it.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": "arn:aws:s3:::my-troubled-bucket"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-troubled-bucket/*"
}
]
}
S3 Bucket Policy Allowing Specific IAM Role to Write:
This bucket policy allows an IAM role from a specific account to perform PutObject actions.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/MyApplicationRole"
},
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::my-troubled-bucket/*"
}
]
}
AWS CLI Commands for Policy Retrieval:
# Get the IAM policy for a user (replace 'MyUser' and 'policy-name')
aws iam get-user-policy --user-name MyUser --policy-name MyS3AccessPolicy
# Get the S3 bucket policy
aws s3api get-bucket-policy --bucket my-troubled-bucket
# Get the ACL for an S3 object
aws s3api get-object-acl --bucket my-troubled-bucket --key path/to/my-object.txt
Environment-Specific Notes
The "403 Forbidden" error can manifest slightly differently depending on your operating environment.
-
Cloud (Production): In production AWS environments, especially those using Infrastructure as Code (IaC) tools like CloudFormation or Terraform, a 403 usually points to an incorrect policy definition in your code. Always review your IAM roles and bucket policies as defined in your IaC templates. CloudTrail is your best friend here, giving you an audit trail of exactly what was denied and why. Ensure you're following the principle of least privilege rigorously.
-
Docker: When running applications in Docker containers that need S3 access, the container itself doesn't typically hold AWS credentials. Instead, it relies on the host's IAM role (e.g., EC2 instance profile) or credentials mounted into the container. If you're getting a 403 in Docker, first verify the underlying host's permissions using
aws sts get-caller-identityfrom within the host, or inspect the container's environment variables for explicitly passed credentials. Often, the EC2 instance role simply lacks the S3 permissions. -
Local Development: On your local machine, S3 access is governed by the credentials configured in your
~/.aws/credentialsfile, or via environment variables (AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN). If you're using temporary credentials (e.g., fromsts assume-role), check their expiration. I've often seen developers forget to re-assume a role after credentials expire, leading to an "Access Denied" that's actually due to invalid (expired) authentication attempting to trigger the authorization check.
Frequently Asked Questions
Q: Is "403 Forbidden" an authentication error?
A: No, it is an authorization error. Your credentials were successfully authenticated, but the identity they represent is not authorized to perform the requested S3 action. An authentication error would typically manifest as 401 Unauthorized or a similar message indicating invalid credentials.
Q: How do I tell if the issue is with my IAM policy or the S3 bucket policy?
A: Start by using the IAM Policy Simulator to test your IAM user/role's permissions against the S3 resource. If the simulator shows an Allow, then the bucket policy is likely restricting access. If the simulator shows a Deny or no Allow, then your IAM policy needs adjustment. CloudTrail logs are also excellent for pinpointing the denying policy.
Q: What's the difference between S3 Bucket Policies and ACLs?
A: S3 Bucket Policies are JSON-based, apply to the entire bucket, and offer fine-grained control over principals, actions, and conditions. ACLs (Access Control Lists) are an older access control mechanism, granting basic read/write permissions to specific AWS accounts or predefined groups. Generally, bucket policies are preferred for managing access, especially for objects, but ACLs still control object ownership and can be a factor in cross-account access scenarios.
Q: Can MFA Delete cause an S3 403 error?
A: Yes, if your S3 bucket has versioning enabled and MFA Delete is configured, any attempt to delete an object (even by the root user) without providing the required MFA token will result in a "403 Forbidden – Access Denied" error.
Q: Why does aws s3 ls s3://my-bucket work, but aws s3 cp s3://my-bucket/object.txt . fail with 403?
A: These two operations require different S3 permissions. aws s3 ls typically requires s3:ListBucket, while aws s3 cp (for downloading) requires s3:GetObject. It's common to have ListBucket but lack GetObject, or vice versa, on a specific resource or for a specific principal. Always verify that your policy explicitly grants all the necessary actions.