Back

Blog details

Bucket Policy vs IAM Policy vs ACL: When to Use Each

AIOZ Network
12 min readSeptember 13, 2026
aioz-storage

A bucket policy attaches to a bucket and controls who can access it. An IAM policy attaches to a user, group or role and controls what that identity can do. Both use the same JSON, S3 checks them together, and only a bucket policy can grant another AWS account access.

TL;DR:

  • An IAM policy attaches to an identity and answers "what can this identity do." A bucket policy attaches to a bucket and answers "who can access this bucket."
  • Only a bucket policy can grant access to a different AWS account. AWS states that you cannot use an identity-based policy for that use case.
  • S3 evaluates every applicable policy together on each request, so an allow in either policy grants access inside one account, and an explicit deny anywhere wins.
  • AIOZ Storage's docs describe sub-users and access grants built on macaroons. They do not document bucket policies, IAM policies or ACLs as features.

What Is the Difference Between a Bucket Policy and an IAM Policy?

Both policy types share the same JSON structure, the same actions and the same conditions, so what they can express is nearly identical. What differs is where the policy lives. AWS describes a bucket policy as "a resource-based policy that is attached to an AWS resource, such as a bucket policy for a bucket." It describes an IAM policy as "an identity-based policy that is attached to an AWS Identity and Access Management (IAM) identity, such as an IAM user, group, or role."

An IAM policy travels with the identity across AWS services, so you can manage a user's whole AWS footprint, S3 included, from one place. A bucket policy stays on the bucket and applies to any requester, which lets it name principals your IAM system has no relationship with. AWS says a bucket policy can "grant other AWS accounts or IAM identities permissions for the bucket and the objects in it." The principal can be another AWS account, an AWS service or, if you write it that way, the public.

A diagram with an IAM user, group or role on the left holding an identity-based IAM policy, an S3 bucket on the right holding a resource-based bucket policy, and a request below that S3 checks against both

When Should You Use a Bucket Policy, an IAM Policy or an ACL?

Start with the job you need done, then pick the tool that fits it:

SituationUseWhy
You manage one user's or role's access to S3 and other AWS servicesIAM policyIt follows the identity everywhere and keeps that identity's permissions in one place.
You have many buckets with different permissions and only a few rolesIAM policyAWS suggests this over keeping a separate bucket policy on every bucket.
Another AWS account needs access to your bucketBucket policyAWS says "You cannot use an identity-based access policy for this use case."
A rule must hold for every requester, such as HTTPS only or an IP or VPC limitBucket policyIt lives on the resource, so it covers requesters you have not thought of, and an explicit deny wins.
Your IAM policies are close to the 20 KB size limitBucket policy, IAM groups or S3 Access PointsAWS names these as the next tools to reach for.
A legacy setup needs control over each object separatelyACLNew buckets disable ACLs by default, and AWS recommends keeping them off unless you need this.

A simple rule of thumb comes out of AWS's own guidance: start with IAM policies. Add a bucket policy when you need cross-account access or a bucket-wide guardrail, and do not create new ACLs. The AWS Security Blog makes the same split and calls ACLs "a legacy access control mechanism that predates IAM."

Size Limits Worth Knowing

AWS states plainly that "both bucket policies and identity-based policies have a 20 KB size limit." A long list of conditions, principals and resource ARNs can reach that ceiling. AWS then points you to other tools such as IAM groups or S3 Access Points instead of one oversized document.

How S3 Evaluates Both Policies Together

A common misconception treats the two as alternatives you pick between. AWS describes the real model directly: "when Amazon S3 receives a request, it must evaluate all of the access policies to determine whether to authorize or deny the request." The policies on the requesting identity and the bucket policy on the target bucket are checked together. A permission that looks correct in one policy can still fail when the other holds a conflicting deny.

The shape of that rule depends on whose account the requester lives in.

Same account: the union, minus any explicit deny. When an IAM user or role requests a resource in its own account, AWS's IAM documentation says the result is "the union of the permissions of the two types. If an action is allowed by an identity-based policy, a resource-based policy, or both, then AWS allows the action. An explicit deny in either of these policies overrides the allow." An IAM policy that allows s3:GetObject is enough on its own, and so is a bucket policy that names that user.

Cross-account: both sides have to agree. S3's authorization documentation says an IAM principal "needs permission from both the parent AWS account to which it belongs and the AWS account that owns the resource." The bucket owner's bucket policy has to grant the other account access, and an administrator in that account still has to allow the user or role through an IAM policy. Granting in only one place gives you 403 Access Denied, which is the most common reason a new cross-account bucket policy "doesn't work."

S3 checks three contexts in order: the user context, the bucket context (the bucket owner's policies, including any explicit deny) and the object context. For an object the bucket owner does not own, S3 still checks the bucket policy for an explicit deny. The bucket owner can deny access to objects in the bucket regardless of who owns them.

Other policy layers only narrow the result, since a permissions boundary on the identity gives "the intersection" with its identity-based policies, and in an AWS Organization, service control policies and resource control policies can limit it further. An explicit deny anywhere wins.

A Worked Example: Two Policies, One Cross-Account Read

Say account 111122223333 owns a bucket named shared-reports, and a role in account 444455556666 needs to read from it. The bucket owner attaches a resource-based policy to the bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowPartnerAccountRead",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::444455556666:root" },
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::shared-reports",
        "arn:aws:s3:::shared-reports/*"
      ]
    }
  ]
}

That covers the bucket side, and the other account's administrator then attaches an identity-based policy to the role, which has no Principal element because the identity it is attached to is the principal:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::shared-reports",
        "arn:aws:s3:::shared-reports/*"
      ]
    }
  ]
}

Two details cause a lot of denied requests. First, s3:ListBucket acts on the bucket itself, so it needs the bucket ARN (arn:aws:s3:::shared-reports), while s3:GetObject acts on objects and needs the /* form. Listing both ARNs, as above, avoids missing one. Second, the resource-based policy needs a Principal naming who is being granted access and the identity-based policy does not, which is a fast way to tell the two types apart.

A Bucket Policy Pattern Worth Knowing: Deny Plain HTTP

Because an explicit deny always wins, a Deny statement suits rules that must hold no matter what other policies say. AWS's example bucket policies include one that rejects unencrypted connections with the aws:SecureTransport condition key, which is false when a request was sent over HTTP:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictToTLSRequestsOnly",
      "Action": "s3:*",
      "Effect": "Deny",
      "Resource": [
        "arn:aws:s3:::shared-reports",
        "arn:aws:s3:::shared-reports/*"
      ],
      "Condition": {
        "Bool": { "aws:SecureTransport": "false" }
      },
      "Principal": "*"
    }
  ]
}

A statement like this belongs on the bucket rather than in each user's IAM policy, because it applies to every requester, including ones you have not thought of yet. That is the case where a resource-based policy is the better place for a rule.

Be careful with the aws:Referer condition key, because AWS warns against using it to keep unauthorized parties out, since "unauthorized parties can use modified or custom browsers to provide any aws:Referer value that they choose." It is meant to stop other sites from hotlinking your content, not to act as access control.

Why You Still Get Access Denied

If a request fails even though a policy appears to allow it, work through the layers in order:

  1. Look for an explicit Deny in the bucket policy, the identity's policies, any permissions boundary and any organization-level SCP, because one deny beats every allow.
  2. For a cross-account request, confirm that both the bucket policy and the requester's IAM policy grant the action.
  3. Check that the ARNs match the action: bucket-level actions like s3:ListBucket need the bucket ARN, and object-level actions like s3:GetObject need the /* ARN.
  4. If you are testing in the console, remember that AWS says it needs extra permissions beyond your object actions, namely s3:ListAllMyBuckets, s3:GetBucketLocation and s3:ListBucket.

Where ACLs Fit: The Older Third Option

S3 has a third access mechanism that predates both policy types: the access control list, or ACL. Every bucket and object has one attached as a subresource, and it names which AWS accounts or predefined groups get access and what kind. The permission set is small and fixed: READ, WRITE, READ_ACP, WRITE_ACP and FULL_CONTROL. AWS describes ACLs as being for basic read and write permissions, "similar to file system permissions," while an access policy supports far more actions. An ACL grantee has to be an AWS account or a predefined group such as AllUsers, never an individual IAM user.

For new buckets this is mostly history, because by default S3 Object Ownership is set to "Bucket owner enforced" and all ACLs are disabled, so the bucket owner owns every object and access is managed only through policies. A request that tries to set an ACL then fails with the AccessControlListNotSupported error. AWS recommends keeping ACLs off "except in circumstances where you need to control access for each object individually," and sharing data outside your account through a bucket policy instead.

If an old application or a log-delivery setup still depends on ACLs, S3 server access logs and CloudTrail record an aclRequired value. That value shows which requests relied on one, so check it before you switch ACLs off.

Does AIOZ Storage Use Bucket Policies or IAM Policies?

AIOZ Storage's docs describe a different model and document no bucket policy, IAM policy or ACL feature. The only use of the word "policies" is one line in the Manage Access Grants tutorial, which says access grants are defined using access control policies attached to your resources. Access control is built on macaroons. The Permissions page says you build permissions from rules of the form subject and actions. The subject is a bucket or an object, and the actions are List, Write, Read and Delete. You can also add time limits, and the permissions are "encapsulated within macaroons" created on the client side, where caveats can narrow them further before you share them.

The docs show two ways to hand out access through the dashboard. The table sets them next to the AWS tool you would reach for:

What you wantAWS toolDocumented AIOZ Storage tool
A teammate with their own login, limited to chosen bucketsIAM user with a policyA member: you assign buckets and one of All, Read, Write, List or Delete per bucket
S3 credentials limited by bucket, permission and timeIAM policyAn access grant: pick S3 Credentials or CLI/API Key, a bucket, a permission and an end date of 24 hrs, 1 week, 1 month, 6 months or 1 year
Withdrawing accessDetach or delete the policyDelete the member, or delete the access grant, which invalidates the primary grant and every child grant derived from it
Another organization or an IP, VPC or HTTPS-only rule on a bucketBucket policyNot documented

Three limits matter when you plan around this. First, revoking a primary access grant revokes everything derived from it, and the docs say per-grant revocation is planned for a later version. Second, deleting a member cannot be undone. Third, I could not find any page listing which S3 policy calls the endpoint accepts, so test PutBucketPolicy against your own account before you depend on it. The S3 Compatibility page is a general statement about the S3 API, not a list of supported operations.

Frequently Asked Questions

What is the difference between a bucket policy and an IAM policy in S3?
The attachment point decides it, because an IAM policy attaches to a user, group or role and travels with that identity across AWS services, and a bucket policy attaches to one bucket and applies to any request against it, whichever identity makes the request.

Can an IAM policy grant access to another AWS account?
No. AWS states that identity-based policies cannot be used for cross-account access, so only a bucket policy or a similar resource-based tool can grant permissions to a separate AWS account.

Does S3 use the bucket policy or the IAM policy when both exist?
S3 uses both together. It evaluates every applicable policy on each request, which means the IAM policies on the requester and any bucket policy on the target bucket. It does not pick one and ignore the other.

What is the size limit on bucket policies and IAM policies?
AWS documents a 20 KB limit for both bucket policies and identity-based policies, and a complex set of conditions can reach that ceiling.

When should you use an IAM policy instead of a bucket policy?
Use an IAM policy when you manage many buckets with a small number of roles, because centralizing permissions in IAM is simpler than maintaining a separate bucket policy per bucket. It also fits when you need to manage the same identity's access to non-S3 services from one place.

When should you use a bucket policy instead of an IAM policy?
Use a bucket policy when you need to grant access to a different AWS account or a specific AWS service principal. It also fits when a rule such as an IP address limit or HTTPS-only access must apply to every requester.

If the bucket policy allows access but my IAM policy does not, is the request allowed?
Inside one account it is, because AWS evaluates the union of identity-based and resource-based policies, so an allow in either one is enough unless an explicit deny exists. For a cross-account request the answer is no, because the requester needs permission from both its own account and the account that owns the bucket.

Does an explicit deny override an allow in S3?
Yes. An explicit Deny in any applicable policy overrides every allow, whether it sits in a bucket policy, an IAM policy, a permissions boundary or an organization SCP.

How do I force HTTPS-only access to an S3 bucket?
Add a bucket policy statement with Effect: Deny, Principal: "*" and a Bool condition where aws:SecureTransport is "false", applied to both the bucket ARN and the /* object ARN. That denies any request sent over plain HTTP.

Why do I get Access Denied when my IAM policy allows s3:GetObject?
Check for an explicit deny elsewhere and confirm the bucket policy also grants access if the request is cross-account. Make sure the policy's Resource uses the bucket/* ARN for object actions. A permissions boundary or SCP can also narrow what the IAM policy grants.

Do bucket policies need a Principal element?
Yes, a resource-based policy has to say who it applies to, while an identity-based IAM policy has no Principal because the identity it is attached to is the principal.

What is the difference between an S3 ACL and a bucket policy?
An ACL is a small per-bucket or per-object list that grants one of five fixed permissions to an AWS account or a predefined group. A bucket policy is a JSON document that can allow or deny many actions with conditions, including for other accounts, and AWS recommends policies for most uses.

Are S3 ACLs disabled by default?
Yes. New buckets use the "Bucket owner enforced" Object Ownership setting, which disables ACLs, so only policies control access, and a request that tries to set an ACL fails with AccessControlListNotSupported.

Does AIOZ Storage use bucket policies or IAM policies?
The docs document neither as a feature, and they describe sub-users (members) and access grants built on macaroons. Permissions are List, Write, Read and Delete on a bucket or object, so confirm any policy call against your own account before relying on it.

References

We only send updates when meaningful changes ship, and you can unsubscribe anytime

Related Content

blog thumbnail

What Is Distributed Storage? How It Works, With Examples

Distributed storage keeps data on many independent nodes that act as one system. See how it works, replication vs erasure coding, its types and trade-offs.

aioz-storage
6 min readNovember 03, 2026
blog thumbnail

What Are Egress Fees? How Download Charges Work in Storage

Egress fees are charges for data leaving a storage provider's network. See what triggers them, how to estimate them, and what AIOZ Storage's docs say.

aioz-storage
6 min readOctober 30, 2026
blog thumbnail

Object Storage Vector Database: Where AIOZ Storage Fits

An object storage vector database keeps vectors in the storage layer, not a cluster. See how it differs, when to pick it, and where AIOZ Storage fits in.

aioz-storage
10 min readOctober 27, 2026
blog thumbnail

AIOZ Storage Vector Database Pricing: What You Pay For

AIOZ Storage vector database pricing line by line: upload, storage, queries, data searched and data returned, with a worked monthly example you can reuse.

aioz-storage
5 min readOctober 23, 2026
blog thumbnail

How to Create a Bucket and Upload a File on AIOZ Storage

Create a bucket and upload a file on AIOZ Storage in five steps: sign up, set a passphrase, add credits, and upload. Plus what the passphrase cannot recover.

aioz-storage
5 min readOctober 20, 2026
blog thumbnail

AIOZ Storage Rate Limit: Working Within 100 Requests/s

The AIOZ Storage rate limit is 100 requests per second. See what the docs do and don't say, and how to stay under it with boto3 retries and a limiter.

aioz-storage
7 min readOctober 16, 2026