How to create an IAM user with read-only access to one S3 bucket
Create an IAM user with no console access, attach an inline policy that allows s3:ListBucket and s3:GetBucketLocation on arn:aws:s3:::my-bucket and s3:GetObject on arn:aws:s3:::my-bucket/*, then create an access key. That is the entire least-privilege policy: no s3:ListAllMyBuckets, no wildcard resources, nothing that can write or delete. Skip the AmazonS3ReadOnlyAccess managed policy; it reads every bucket in the account. When you paste the keys into S3 Viewer it notices the user cannot list buckets, asks you to name the one it can read, checks it with HeadBucket, and connects.
Step-by-step.
- 01
Decide what read-only has to cover
Listing and downloading is the baseline:s3:ListBucket,s3:GetBucketLocationands3:GetObject. Adds3:GetObjectVersionands3:ListBucketVersionsif the bucket is versioned and old versions matter, andkms:Decrypton the key if objects use SSE-KMS. Leave outs3:ListAllMyBuckets: it can only be granted on*and reveals every bucket name in the account. - 02
Write the policy
Two statements, two ARNs. Bucket-level actions go onarn:aws:s3:::my-bucket; object-level actions go onarn:aws:s3:::my-bucket/*. Swapping them is the classic way to end up with Access Denied. Replacemy-bucketin both places.{ "Version": "2012-10-17", "Statement": [ { "Sid": "ListTheBucket", "Effect": "Allow", "Action": ["s3:ListBucket", "s3:GetBucketLocation"], "Resource": "arn:aws:s3:::my-bucket" }, { "Sid": "ReadObjects", "Effect": "Allow", "Action": ["s3:GetObject", "s3:GetObjectVersion"], "Resource": "arn:aws:s3:::my-bucket/*" } ] } - 03
Create the user and attach the policy (console)
IAM → Users → Create user. Name it for its job, for examples3-viewer-readonly, and leave “Provide user access to the AWS Management Console” unchecked. On the permissions step choose Attach policies directly → Create policy, switch to the JSON tab, paste the policy above, name itS3ReadOnly-my-bucket, then return to the user wizard, refresh the list, tick the new policy and finish. - 04
Or do the same from the CLI
Save the JSON ass3-readonly.json. An inline policy (put-user-policy) is deleted with the user, which is what you want for a single-purpose identity.aws iam create-user --user-name s3-viewer-readonly aws iam put-user-policy \ --user-name s3-viewer-readonly \ --policy-name S3ReadOnly-my-bucket \ --policy-document file://s3-readonly.json - 05
Create an access key
Open the user → Security credentials → Create access key. Pick “Application running outside AWS” or “Third-party service”, then copy both values. The secret is shown exactly once. A user can hold two keys at a time, which is what makes rotation possible later.aws iam create-access-key --user-name s3-viewer-readonly # "AccessKeyId": "AKIA...", "SecretAccessKey": "..." (shown once) - 06
Test the boundary
Prove that it can read the bucket and nothing else. The second and third commands should fail with AccessDenied.aws configure --profile readonly # paste the new keys aws s3 ls s3://my-bucket/ --profile readonly # works aws s3 ls --profile readonly # AccessDenied (expected) aws s3 cp ./x.txt s3://my-bucket/x.txt --profile readonly # AccessDenied (expected) - 07
Connect it in S3 Viewer
Connect server → Endpointhttps://s3.us-east-1.amazonaws.com(use the bucket's region), pick the region, paste the access key ID and secret. Because the user cannot list buckets, S3 Viewer shows a Bucket names field: typemy-bucket, press Check and it verifies the name withHeadBucket, then Connect. The keys are encrypted in your browser with the server's public key before they are sent. Teammates you invite afterwards get a Viewer or Editor role on top of this key and never see the AWS credentials. - 08
Harden and maintain it
Narrow the grant to a prefix with ans3:prefixcondition if the user only needs one folder. Add anaws:SourceIpcondition when the caller has a fixed address. Rotate keys by creating the second key, swapping it in, then deactivating and deleting the first. For humans, prefer roles through IAM Identity Center; reserve IAM users with static keys for tools like this that need them, one user per tool.
What's actually happening.
S3 permissions come in two shapes. Bucket-level actions (ListBucket, GetBucketLocation, ListBucketVersions) are evaluated against the bucket ARN, arn:aws:s3:::my-bucket. Object-level actions (GetObject, PutObject, DeleteObject) are evaluated against object ARNs, which is why they need the /* suffix. A single statement cannot cover both, so the smallest correct policy always has two. ListAllMyBuckets is a third shape, account-wide with no resource to scope, which is why least-privilege policies leave it out.
S3 Viewer is built for keys like this. When you connect, it first tries ListBuckets; on AccessDenied it switches to asking for bucket names and calls HeadBucket on each one, so a bucket-scoped or prefix-scoped key works without any wildcard grants. The key is encrypted in the browser with the server's public key before it leaves the page, and every request S3 Viewer signs on your behalf is still bounded by this IAM policy: a Viewer in the app cannot do more than the key can.
The practical pattern for teams is one read-only IAM user per connected bucket, then S3 Viewer invites for people. IAM stays small, the AWS key never leaves the server, and access changes are made in the app instead of in the IAM console.
Common questions.
What is the minimum IAM policy for read-only access to one S3 bucket?
Do I need s3:ListAllMyBuckets for a read-only user?
Should I just attach the AmazonS3ReadOnlyAccess managed policy?
Why does aws s3 ls give Access Denied with my read-only user?
How do I limit read-only access to one folder in the bucket?
Should this be an IAM user or an IAM role?
How do I create read-only credentials for a Cloudflare R2 bucket?
Can I give a teammate read-only access without creating an IAM user for them?
Can a read-only user open encrypted objects?
Skip the CLI. Try it in the browser.
S3 Viewer turns the steps above into a single click. Open source, self-hostable, free.
Why teams pick this
More how-tos
Granular permissions
The IAM s3:prefix Condition that everyone misses, plus when workspace roles are simpler than IAM.
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.