S3 IAM vs Bucket Policies: Differences, Examples, and When to Use Each
S3 uses two separate policy systems: IAM identity-based policies attached to users and roles, and bucket policies attached to the bucket itself. Knowing which one to use, and when you need both, is one of the most practical S3 skills you can build.
Simple explanation
An IAM policy answers the question: “What is this identity allowed to do?” It is attached to an IAM user, group, or role, and can grant or deny access to any AWS service including S3. IAM policies are the right tool when you want to control what your application roles, developers, or automation accounts can do inside your own AWS account.
A bucket policy answers the question: “Who is allowed to access this bucket?” It is attached directly to the S3 bucket, not to any user. Because it lives on the bucket, it can grant access to principals outside your account: other AWS accounts, AWS service principals like CloudFront, or even anonymous public users.
The two systems overlap. Both can allow or deny the same S3 actions. The difference is where the policy is attached and what kinds of principals it can reach.
For access within a single AWS account, an IAM policy on the role or user is usually all you need. For anything involving other accounts, AWS services, or bucket-wide security controls like enforcing HTTPS, you need a bucket policy.
Quick answer
- IAM policies are attached to identities (users, roles, groups).
- Bucket policies are attached to buckets.
- Same-account access: an IAM policy on the role is enough.
- Cross-account access: you need a bucket policy on your side, and an IAM policy on the caller’s side.
- Service principals (CloudFront, Config): always use a bucket policy.
- An explicit Deny in either policy wins, regardless of what the other says.
IAM policies vs bucket policies at a glance
| Feature | IAM policy | Bucket policy |
|---|---|---|
| Where it is attached | IAM user, group, or role | The S3 bucket |
| Who it controls | The identity it is attached to | Any principal named in the policy |
| Same-account access | Yes, sufficient on its own | Optional, not required |
| Cross-account access | Needed on the caller’s side | Required on the bucket side |
| AWS service principals | Not applicable | Yes (e.g., CloudFront, Config) |
| Public (anonymous) access | Not possible | Yes, with "Principal": "*" and Block Public Access disabled |
| HTTPS enforcement | Possible but rarely used this way | Common: deny non-HTTPS requests bucket-wide |
| Operational scope | Across all AWS services | This bucket only |
| Typical use | App role, developer access, least privilege | Cross-account, CDN access, security rules |
When to use IAM policies
IAM policies are the right choice for granting access to identities inside your own AWS account. They are managed through IAM, which means you can apply the same policy to multiple roles or update access centrally without touching the bucket.
Use an IAM policy when:
- Your application role needs S3 access. Attach a policy to the Lambda function’s execution role or EC2 instance profile. The policy can scope access to a specific bucket and prefix. No bucket policy required.
- A developer or team needs read access. Attach the policy to the IAM user, group, or SSO permission set. Keep it scoped to the specific buckets and prefixes they need.
- You want least-privilege access to a prefix. IAM policies support
Resourcewith prefix-level ARNs likearn:aws:s3:::my-bucket/uploads/*, so you can grant write access to one folder without exposing the whole bucket. See the least privilege guide for patterns. - You want to reuse permissions across multiple resources. An IAM policy can reference multiple S3 buckets, letting one role access the buckets it needs without defining a policy on each bucket separately.
For the mechanics of how IAM policies are structured, see IAM policy structure.
When to use bucket policies
Bucket policies are required any time the entity accessing the bucket is not an IAM identity in your account.
Use a bucket policy when:
- Another AWS account needs access. A bucket policy is the only way to grant access to an external account. The external account’s user or role also needs an IAM policy on their side, and both sides must allow.
- CloudFront or another AWS service needs to read objects. Services like CloudFront authenticate with a service principal, not an IAM role in your account. A bucket policy is the correct mechanism.
- You need to enforce HTTPS across all principals. A bucket policy with a
Denyon non-HTTPS requests applies to every caller, including those allowed by IAM policies. This is the standard pattern for bucket-wide security enforcement. - You want to centrally manage who can access one bucket. If access decisions should live on the bucket itself for auditing, compliance, or simplicity, a bucket policy keeps everything in one place.
- You need to allow public access. Only a bucket policy with
"Principal": "*"can make objects publicly readable. Block Public Access must also be configured appropriately at the account and bucket level.
For a broader view of S3 security controls, see S3 security best practices.
How S3 permission evaluation works
When a request arrives at S3, AWS evaluates all applicable policies and determines the result in this order:
1. Explicit Deny wins.
If any policy (IAM or bucket) contains an explicit "Effect": "Deny" that matches the request, the request is denied. No other policy can override an explicit Deny.
2. Allow grants access.
If any applicable policy grants an "Effect": "Allow" and there is no explicit Deny, the request is allowed.
3. Implicit Deny is the default. If no policy explicitly allows the action, the request is denied. This is the default for all S3 actions.
Same-account access: When an IAM user or role in the same account makes a request, S3 checks both the IAM policy and any bucket policy that exists. Either one can grant access. An explicit Deny in either overrides any Allow elsewhere.
Cross-account access: When a principal from an external account makes a request, both sides must independently allow the action. The bucket policy must allow the external principal, and the external account’s IAM policy must allow the S3 actions. If either side is missing an Allow, the request is denied even if the other side is fully permissive.
A common mistake is to write a permissive bucket policy for another account and assume that is all that is needed. The other account’s IAM policy must also explicitly allow the S3 actions. Without both sides allowing, the request is denied. There are no exceptions to this rule.
Same-account access is like an internal office: if your company badge works, you can enter any room the building allows. Cross-account access is more like a visitor from another company: the building still needs to let you in, and your own organisation also needs to authorise the visit. Both gates must open.
The explicit Deny rule
An explicit “Effect”: “Deny” in any policy overrides every Allow, everywhere. It does not matter whether the Allow is in an IAM policy, a bucket policy, or both. If a Deny matches the request, the request is rejected. This is the most important rule in S3 access control.
This matters most in three situations:
- HTTPS enforcement. A bucket policy with a Deny on non-HTTPS requests applies to every caller, including those with IAM Allows.
- Overly broad Deny statements. A poorly scoped Deny on
"Principal": "*"can accidentally block AWS services from writing to your bucket. - SCPs. A service control policy in AWS Organizations can deny S3 actions before the request even reaches your IAM policy or bucket policy.
Real examples
Same-account IAM role: read and write to a prefix#
Attach this IAM policy to the role used by your application server or Lambda function. No bucket policy is needed for same-account access.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AppRoleUploadsAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::my-app-assets/uploads/*"
},
{
"Sid": "AppRoleListPrefix",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-app-assets",
"Condition": {
"StringLike": {
"s3:prefix": "uploads/*"
}
}
}
]
}CloudFront: read objects from a private bucket#
This bucket policy allows a specific CloudFront distribution to read objects. The Condition block scopes access to one distribution so no other CloudFront distribution can access the bucket. See S3 buckets explained for background on origin access control.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCloudFrontOAC",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-static-site/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EDFDVBD6EXAMPLE"
}
}
}
]
}Cross-account: allow an external account to upload objects#
This bucket policy allows an IAM role in account 987654321098 to upload objects. That account’s IAM policy must also allow the S3 actions. The bucket policy alone is not enough.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "CrossAccountUpload",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::987654321098:role/DataPipelineRole"
},
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": "arn:aws:s3:::my-shared-bucket/incoming/*"
}
]
}Deny all non-HTTPS requests#
This bucket policy applies to every principal, including those allowed by IAM policies. Because it uses an explicit Deny, it cannot be overridden.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonHTTPS",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-app-assets",
"arn:aws:s3:::my-app-assets/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}IAM policies vs bucket policies vs ACLs
S3 also has a third access control mechanism: access control lists (ACLs). ACLs predate IAM and bucket policies and are now considered legacy.
ACLs are applied per-object and use a fixed set of permissions (READ, WRITE, FULL_CONTROL). They cannot express conditions, cannot reference IAM roles, and are harder to audit than policies. For most workloads, you should not use them.
Object Ownership is the setting that controls whether ACLs are active at all. When Object Ownership is set to Bucket owner enforced, the current AWS default for new buckets, ACLs are disabled entirely and all access control is handled through IAM policies and bucket policies.
- You are receiving objects from a third party that uploads with an ACL (for example,
bucket-owner-full-control). You may need to accept the ACL to take ownership of the object. - You are working with a bucket created before Object Ownership defaults changed, where ACLs may still be active.
For any new bucket or access control design, use IAM policies and bucket policies. They are more expressive, consistent with the rest of AWS access control, and far easier to audit. For conditions-based access patterns, such as restricting access by IP, VPC, MFA, or encryption state, see IAM policy conditions.
Practical use cases
Internal application in one AWS account Attach an IAM policy to the application’s execution role. Scope it to the specific bucket and prefix. No bucket policy is needed unless you also want to enforce HTTPS or restrict VPC access.
Sharing a bucket with another AWS account Add a bucket policy allowing the external account’s role. Confirm that the external account also has an IAM policy allowing the S3 actions. Without both, the request will be denied.
Serving content through CloudFront Use Origin Access Control (OAC) and a bucket policy that allows the CloudFront service principal scoped to your specific distribution. Keep the bucket private and do not use a public bucket policy.
Enforcing organisation-wide bucket access rules
Use a bucket policy with Deny statements for broad security controls, for example denying HTTP access or requiring encryption on upload. These controls apply to all callers regardless of their IAM policies.
Troubleshooting an S3 access denied error Access denied errors are often caused by a missing Allow in either the IAM policy or the bucket policy, or by an explicit Deny in one overriding the other. Use the troubleshooting checklist below as a starting point. CloudTrail records S3 data events and provides the exact evaluation reason when access is denied.
VPC endpoint restricting S3 access When traffic routes through a VPC endpoint for S3, the endpoint may have its own policy controlling which buckets it can reach. This is separate from both IAM policies and bucket policies. See VPC endpoint policies for how endpoint policies interact with the rest of access evaluation.
Common mistakes
-
Assuming same-account access always needs a bucket policy. It does not. An IAM policy on the role is sufficient for same-account access. Adding a bucket policy is optional and can add complexity without benefit.
-
Forgetting that cross-account access requires both sides to allow. The bucket policy on your side is not enough. The external account must also have an IAM policy granting the S3 actions. Either-side denials mean the whole request is denied.
-
Applying a public-read bucket policy without checking Block Public Access. Block Public Access at the account or bucket level will override the policy and prevent public access from working. Always check Block Public Access settings first.
-
Writing overly broad Deny statements that block AWS services. A Deny on
"Principal": "*"with loose conditions can accidentally block services like CloudTrail or AWS Config from writing to the bucket. Scope Deny conditions carefully and test after applying. -
Not checking service principal conditions. A bucket policy that allows
"Service": "cloudfront.amazonaws.com"without a condition scoped to your distribution allows any CloudFront distribution, including those in other accounts, to access your bucket. Always add anAWS:SourceArncondition. -
Confusing bucket policy behaviour with VPC endpoint policy, SCP, or ACL behaviour. S3 access evaluation involves multiple independent controls. A bucket policy allow does not override an SCP Deny. A bucket policy allow does not bypass a VPC endpoint policy restriction. Each layer is evaluated independently. See service control policies for how SCPs interact with IAM.
-
Using ACLs on new buckets. Modern S3 buckets default to
Bucket owner enforced, which disables ACLs. Attempting to set object ACLs on these buckets will fail. Use bucket policies for access control instead.
Troubleshooting checklist
When you receive an S3 AccessDenied error, work through these checks:
- Who is the caller? Run
aws sts get-caller-identityto confirm the IAM principal making the request. Access denied errors are sometimes caused by the wrong role being assumed. - Does the IAM policy allow the action? Check the IAM policy attached to the role or user. Confirm the correct bucket ARN and action are included.
- Does the bucket policy allow or deny the caller? Check the bucket policy using
aws s3api get-bucket-policy --bucket <name>. Look for explicit Deny statements that may match the caller. - Is there an explicit Deny anywhere? An explicit Deny in any policy, IAM, bucket policy, SCP, or permission boundary, overrides all Allows.
- Is Block Public Access blocking a public policy? Check account-level and bucket-level Block Public Access settings if you are trying to enable public access.
- Is Object Ownership preventing ACL-based access? If you are relying on ACLs, check whether Object Ownership is set to
Bucket owner enforced. - Is a VPC endpoint policy restricting access? If the request routes through a VPC endpoint, the endpoint policy may not allow the bucket or action. Check the endpoint policy separately.
- Is an SCP restricting S3 actions? If the account is part of an AWS Organisation, a service control policy may be denying the action before it reaches the bucket. Check SCPs in AWS Organizations.
- Check CloudTrail for the exact reason. S3 data events logged in CloudTrail include an
errorCodeanderrorMessagethat identify which policy caused the denial.
Summary
- IAM policies are attached to identities and control what those identities can do.
- Bucket policies are attached to the bucket and control which principals can access it.
- For same-account IAM users and roles, an IAM policy alone is sufficient.
- For cross-account access or AWS service principals, use a bucket policy. Cross-account access also requires an IAM policy on the caller’s side.
- Explicit Deny in any policy wins. It cannot be overridden by an Allow anywhere else.
- Use bucket policies to enforce HTTPS, allow CloudFront, or grant access to other AWS accounts.
- ACLs are legacy. For new buckets, use IAM policies and bucket policies.
- When debugging access denied errors, check the caller identity, both policies, Block Public Access, VPC endpoint policy, and SCPs.
Frequently asked questions
What is the difference between an IAM policy and a bucket policy in S3?
An IAM policy is attached to an identity (user, group, or role) and controls what that identity can do across AWS services. A bucket policy is attached to the S3 bucket itself and controls which principals, including those in other AWS accounts or AWS services, can access that bucket. Both can allow or deny S3 actions, but they are applied differently and suit different scenarios.
Do I need both an IAM policy and a bucket policy?
Not always. For IAM users and roles within the same AWS account, an IAM policy alone is sufficient and no bucket policy is required. You need a bucket policy when granting access to a different AWS account, an AWS service principal such as CloudFront, or anonymous public users. For same-account access, using both is fine but not required.
Can a bucket policy grant cross-account access by itself?
A bucket policy can allow a principal from another account to access the bucket, but the other account must also have an IAM policy allowing the S3 actions. Cross-account access requires both sides to allow: the bucket policy on your side, and an IAM policy on the caller's side. A bucket policy alone is not sufficient.
Can a bucket policy make a bucket public?
A bucket policy can grant public access using "Principal": "*", but only if the account-level and bucket-level Block Public Access settings allow it. Block Public Access takes precedence and will override bucket policies that attempt to grant public access. Always check Block Public Access before applying any public policy.
Should I use bucket policies or ACLs for S3 access control?
Use bucket policies. ACLs are a legacy mechanism that AWS recommends against for most use cases. With Object Ownership set to Bucket owner enforced, ACLs are disabled entirely and all access control is handled through policies. Bucket policies are more expressive, easier to audit, and work consistently with the rest of AWS IAM.