AWS S3 Buckets
Creating and configuring S3 buckets. Global naming rules, the us-east-1 exception, and how to delete a bucket that will not delete.
A bucket is the top-level container for objects. Creating one takes a single command; creating one you will not regret takes about six settings.
Naming rules
Bucket names are globally unique across every AWS account, not just yours. That is why obvious names fail. Someone claimed logs and backups in 2006.
| Rule | Detail |
|---|---|
| Length | 3, 63 characters |
| Characters | Lowercase letters, numbers, hyphens, dots |
| Start and end | A letter or number |
| Not permitted | Uppercase, underscores, formatted as an IP address |
| Prefixes to avoid | xn--, sthree- |
| Suffixes to avoid | -s3alias, --ol-s3 |
Avoid dots entirely. A bucket name containing a dot breaks virtual-hosted-style HTTPS access, because the wildcard certificate does not match multi-level subdomains, which produces a TLS error that looks nothing like a naming problem.
The practical convention is to include your account ID or organisation name:
acme-artifacts-111122223333
acme-prod-logs-eu-west-1
Creating one
aws s3api create-bucket \
--bucket acme-artifacts-111122223333 \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1
us-east-1 is the exception. It must not have a LocationConstraint, and passing one fails with InvalidLocationConstraint:
aws s3api create-bucket --bucket acme-artifacts-111122223333 --region us-east-1
This inconsistency exists because us-east-1 was the original region and its API behaviour predates the concept.
Region matters more than people expect
A bucket lives in one region and the data stays there. Three consequences:
- Latency: a bucket far from your compute is slow.
- Cost: cross-region requests and transfer are charged more than same-region.
- Compliance: data residency requirements are satisfied by region choice.
Put the bucket in the same region as whatever reads it most.
The settings to apply at creation
Some of these cannot be added later, and the rest are tedious to retrofit.
BUCKET=acme-artifacts-111122223333
# 1. Block all public access. Four settings, all on
aws s3api put-public-access-block --bucket $BUCKET \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"
# 2. Disable ACLs. The mechanism behind most historic S3 exposures
aws s3api put-bucket-ownership-controls --bucket $BUCKET \
--ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
# 3. Versioning. Cannot be undone, so decide deliberately
aws s3api put-bucket-versioning --bucket $BUCKET \
--versioning-configuration Status=Enabled
# 4. Encryption with a bucket key to keep KMS costs sane
aws s3api put-bucket-encryption --bucket $BUCKET \
--server-side-encryption-configuration '{
"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"},"BucketKeyEnabled":true}]}'
# 5. Lifecycle, at minimum, clean up failed multipart uploads
aws s3api put-bucket-lifecycle-configuration --bucket $BUCKET \
--lifecycle-configuration file://lifecycle.json
Good news on defaults: Block Public Access is on by default for new buckets, ACLs are disabled by default under Bucket owner enforced ownership, and SSE-S3 encryption is applied automatically. The commands above are worth running anyway to make the intent explicit in code rather than relying on a default that could differ across accounts.
The lifecycle rule for incomplete multipart uploads belongs on every bucket. Failed uploads leave parts billed as storage that never appear in a listing, and teams find them years later as an unexplained cost.
Object Lock is creation-only
If you need genuine immutability, ransomware protection, regulatory retention, Object Lock must be enabled when the bucket is created. It cannot be added afterwards, and retrofitting means creating a new bucket and copying everything.
aws s3api create-bucket \
--bucket acme-backups-111122223333 \
--region eu-west-1 \
--create-bucket-configuration LocationConstraint=eu-west-1 \
--object-lock-enabled-for-bucket
Folders do not exist
The console shows folders, but S3 has none. logs/2026/09/app.log is a single flat key that happens to contain slashes.
| Expectation | Reality |
|---|---|
| Deleting a folder | Deleting every object under that prefix |
| An empty folder | Does not exist unless a zero-byte object was created |
| Renaming a folder | Copying every object and deleting the originals |
| Listing a folder | A prefix scan, which is slow over millions of keys |
Prefixes do matter for one real reason: S3 scales request rate per prefix, at roughly 3,500 writes and 5,500 reads per second each. A high-throughput workload writing everything under one prefix will throttle.
Deleting a bucket
A bucket must be empty before it can be deleted, which is a useful accident-preventer.
# Delete objects and the bucket in one step
aws s3 rb s3://$BUCKET --force
That --force deletes every object first. On a versioned bucket it is not enough. Versions and delete markers remain, and rb still fails:
# Remove all versions
aws s3api delete-objects --bucket $BUCKET --delete "$(aws s3api list-object-versions \
--bucket $BUCKET --output json \
--query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}')"
# Remove all delete markers
aws s3api delete-objects --bucket $BUCKET --delete "$(aws s3api list-object-versions \
--bucket $BUCKET --output json \
--query '{Objects: DeleteMarkers[].{Key:Key,VersionId:VersionId}}')"
aws s3api delete-bucket --bucket $BUCKET
A gentler alternative for large buckets is a lifecycle rule expiring everything, then deleting the bucket a day later. S3 does the deletion in the background at no request cost.
Note that a deleted bucket name is not immediately reusable, and in a different region it may take time to become available.
Go deeper in this section
| Sub-topic | What you will learn |
|---|---|
| S3 Permissions | Bucket policies, IAM and public access |
| S3 Versioning | Version history, delete markers, recovery |
Quick reference
| Task | Command |
|---|---|
| Create | aws s3api create-bucket --bucket <b> --region <r> --create-bucket-configuration LocationConstraint=<r> |
| Create in us-east-1 | aws s3api create-bucket --bucket <b> --region us-east-1 |
| List buckets | aws s3 ls |
| Bucket region | aws s3api get-bucket-location --bucket <b> |
| Size and object count | aws s3 ls s3://<b> --recursive --summarize --human-readable |
| Public access settings | aws s3api get-public-access-block --bucket <b> |
| Empty and delete | aws s3 rb s3://<b> --force |
| Orphaned uploads | aws s3api list-multipart-uploads --bucket <b> |
Key takeaways
- Bucket names are globally unique across all of AWS. Include your account ID.
- Avoid dots in names; they break virtual-hosted-style HTTPS with a confusing TLS error.
us-east-1must omitLocationConstraint: every other region requires it.- Object Lock can only be enabled at creation. Decide up front for anything holding backups.
- Add an
AbortIncompleteMultipartUploadlifecycle rule to every bucket. - There are no folders: keys are flat strings, and prefixes affect request-rate scaling.
aws s3 rb --forcedoes not empty a versioned bucket. Versions and delete markers need removing first.
Check your understanding
A create that fails in one region
The same create-bucket command works in eu-west-1 but fails with InvalidLocationConstraint in us-east-1. Why?
A bucket that will not delete
aws s3 rb s3://my-bucket --force reports the bucket is not empty, but a listing shows no objects. What is going on?
Frequently Asked Questions
Why does my S3 bucket name fail with "already exists"?
Bucket names are globally unique across every AWS account, so a short or obvious name was almost certainly claimed years ago, and you get the same error whether it belongs to you or to a stranger. Names must also be 3 to 63 characters, lowercase, and DNS-compatible. Including your account ID or organisation prefix, as in acme-artifacts-111122223333, makes collisions essentially impossible and also makes ownership obvious when browsing a long list.
Why does creating a bucket in us-east-1 behave differently?
us-east-1 was the original S3 region and predates the LocationConstraint parameter, so the API treats it as the implicit default and rejects an explicit value with InvalidLocationConstraint. Every other region requires the parameter. Scripts written for one region and reused for another hit this constantly, so it is worth handling explicitly if you provision across regions.
Should I use dots in bucket names?
No. A name containing a dot breaks virtual-hosted-style HTTPS access, because AWS's wildcard certificate does not match multi-level subdomains, so requests fail with a TLS error that gives no hint about naming. Dots were historically used to match a custom domain for static website hosting, but the modern approach is CloudFront in front of a private bucket, which removes the reason entirely. Use hyphens.
What settings should I apply to a new S3 bucket?
Block Public Access on all four settings, Bucket owner enforced ownership to disable ACLs, default encryption with a bucket key enabled, and a lifecycle rule that aborts incomplete multipart uploads. Most of these are now defaults for new buckets, but applying them explicitly in your infrastructure code makes the intent visible and protects against differences between accounts. Decide about versioning and Object Lock at creation, since Object Lock cannot be added later and versioning cannot be undone.
How do I delete a bucket that will not empty?
If versioning is or was ever enabled, aws s3 rb --force is not enough. It removes current objects but leaves noncurrent versions and delete markers, which block deletion while being invisible in a normal listing. Delete them explicitly using list-object-versions piped into delete-objects for both Versions and DeleteMarkers. For very large buckets, a lifecycle rule expiring everything is cheaper and easier: S3 removes the objects in the background at no request cost, and the bucket deletes cleanly a day or two later.
Do folders exist in S3?
No. The console renders folders for convenience, but a key like logs/2026/app.log is a single flat string containing slashes. Deleting a folder means deleting every object with that prefix, renaming one means copying and deleting every object, and an empty folder only exists if someone created a zero-byte object to represent it. Prefixes do matter for performance, though: S3 scales request rate per prefix, so spreading high-volume writes across prefixes avoids throttling.