How to set granular S3 permissions per prefix
TL;DR
AWS IAM is the right tool for fine-grained per-prefix access — write a policy with two statements: s3:GetObject on the prefix wildcard, and s3:ListBucket on the bucket itself with a Condition on s3:prefix. The s3:prefix condition on listing is the part most people miss, which is why first-attempt policies let users read but not list. For human collaboration, S3 Viewer offers a simpler alternative: per-bucket roles assigned by email invite — Owner, Admin, Editor, or Viewer — without writing IAM at all.
Steps
Step-by-step.
- 01
Decide the scope
Bucket, prefix, or single key. The smaller the scope, the safer. - 02
IAM: write the read statement
Allows3:GetObjecton every key inside the prefix.Resourceuses a wildcard."Resource": "arn:aws:s3:::my-bucket/reports/finance/*" - 03
IAM: add the listing condition (the part most people miss)
Listing requiress3:ListBucketon the bucket itself (not the prefix), with aConditionons3:prefix. This is why most first-attempt IAM policies let users read but not list.{ "Version": "2012-10-17", "Statement": [ { "Sid": "ReadInsidePrefix", "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::my-bucket/reports/finance/*" }, { "Sid": "ListInsidePrefix", "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::my-bucket", "Condition": { "StringLike": { "s3:prefix": ["reports/finance/*"] } } } ] } - 04
IAM: attach to a user or role and test
Attach the policy to the IAM user or role and verify with the IAM policy simulator before handing it over. Confirm both list and get work inside the prefix and deny outside. - 05
Team workflows: S3 Viewer per-bucket roles
Authoring an IAM policy per teammate is heavy for human collaboration. For team access, add the server once with a least-privilege credential and give each member a role: Viewer (browse and download), Editor (also upload, rename, delete and create buckets where supported) or Admin (also manage members, rename the server and remove buckets from it). Owner is not assignable — it stays with whoever created the server, and only an Owner can delete it. A bucket can override a member's server role. S3 Viewer enforces those roles on the server; for finer per-prefix scoping within a bucket, layer in IAM as above. - 06
Verify
Try to list and read outside the prefix. Both should deny. If they don't, the policy is too broad — most likely theResourcewildcard is wrong or the listingConditionis missing.
Under the hood
What's actually happening.
S3 has no folders — “prefix-scoped” access is enforced by IAM policy on the key pattern. The catch is that listing requires a separate s3:ListBucket permission on the bucket ARN scoped via Condition/s3:prefix, which trips up most people writing their first IAM policy. For team collaboration, S3 Viewer offers an alternative: add the server once with one least-privilege key and give invited members a role. Those roles are enforced on our server before any S3 call is signed, so you don't have to write a new IAM policy every time you onboard someone.
FAQ
Common questions.
How do I give someone access to one folder in an S3 bucket?
Why does my IAM policy let users read S3 files but not list the folder?
What's the difference between s3:ListBucket and s3:GetObject?
Can I scope down to a single S3 key?
Do I need a custom IAM policy for every teammate?
Does this work with Cloudflare R2?
Use S3 Viewer for this
Skip the CLI. Try it in the browser.
S3 Viewer turns the steps above into a single click. Open source, self-hostable, free.
Use case
Why teams pick this
Related guides
More how-tos
Read-only IAM user
The exact least-privilege policy JSON, the console and CLI steps, and how to connect the keys without s3:ListAllMyBuckets.
Fix Access Denied
A nine-point checklist: IAM actions and ARNs, bucket policy denies, Block Public Access, KMS keys, object ownership, and the 403-that-is-really-a-404.
Invite a teammate
Skip per-person IAM users for human collaboration. Email invite, per-bucket role, one-click revoke.