How to set up an Amazon S3 bucket for server backups
A backup bucket on Amazon S3 should be private, encrypted, versioned and able to delete old copies by itself. Create it in a Region away from your servers with the default Block Public Access and SSE-S3 encryption, give each server an IAM identity limited to one prefix, and add a lifecycle rule that expires old backups, old versions and unfinished uploads. Then upload with aws s3 cp or aws s3 sync and prove a download matches.
Create the bucket
Plan for one bucket with one prefix per server, such as web-01/. A bucket's name and Region are permanent.
- Open the S3 console and choose the Region in the navigation bar, ideally one where your servers don't run.
- Choose General purpose buckets, then Create bucket.
- Enter a Bucket name: 3 to 63 lowercase letters, numbers and hyphens, starting and ending with a letter or number. Periods are allowed, but AWS recommends avoiding them. The name shows in object URLs, so keep it free of anything sensitive.
- Under Object Ownership, keep Bucket owner enforced, so ACLs stay off and policies alone control access.
- Under Block Public Access settings for this bucket, keep all four settings on. They are on by default.
- Under Bucket Versioning, choose Enable (see below).
- Under Default encryption, keep SSE-S3. It costs nothing. SSE-KMS adds AWS KMS charges and request quotas.
- Leave Object Lock under Advanced settings off unless you have read the Object Lock section below. Once on, it can't be turned off.
- Choose Create bucket.
The same bucket from the CLI, run with admin credentials:
aws s3api create-bucket --bucket acme-server-backups --region eu-central-1 --create-bucket-configuration LocationConstraint=eu-central-1--regionandLocationConstraintmust name the same Region. Forus-east-1, drop--create-bucket-configuration; every other Region needs it.- New buckets get Block Public Access, disabled ACLs and SSE-S3 without extra flags.
aws s3api put-bucket-versioning --bucket acme-server-backups --versioning-configuration Status=Enabledaws s3api get-public-access-block --bucket acme-server-backupsThe second command should show BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy and RestrictPublicBuckets all true. AWS recommends waiting 15 minutes after first enabling versioning before writing objects.
Give the server a least-privilege credential
Never put root or admin keys on a server. On EC2, attach an IAM role to the instance through an instance profile, and programs on it get temporary credentials. Elsewhere, use an IAM user with an access key, or IAM Roles Anywhere, which AWS suggests for machines outside AWS. Either way, attach this policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListOwnPrefix",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::acme-server-backups",
"Condition": { "StringLike": { "s3:prefix": ["web-01/*"] } }
},
{
"Sid": "ReadWriteOwnPrefix",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:DeleteObject",
"s3:AbortMultipartUpload"
],
"Resource": "arn:aws:s3:::acme-server-backups/web-01/*"
}
]
}s3:ListBucketis a bucket action, so itsResourceis the bucket ARN without/*. Thes3:prefixcondition allows listings underweb-01/only, which is whataws s3 lsandaws s3 syncrequest for that path.s3:PutObjectuploads, including each part of a multipart upload.s3:GetObjectdownloads, for restores and checks.s3:DeleteObjectlets your tool prune old backups. In a versioned bucket it only adds a delete marker, because the policy doesn't grants3:DeleteObjectVersion. Drop it if lifecycle rules do the pruning.s3:AbortMultipartUploadlets the CLI clean up a failed large upload.- Object actions take the object ARN, ending in
/*. Give each server its own user and prefix.
In the IAM console, create a policy from this JSON and attach it to a new user without console access. On the user's Security credentials tab, choose Create access key. Then, on the server:
aws configure --profile backupsThe secret access key is shown once, and the CLI stores it in plain text in ~/.aws/credentials. Keep that file readable only by the user that runs backups, with chmod 600.
Versioning: what it buys you
With versioning on, an overwrite keeps the old object as a noncurrent version, and a delete without a version ID only adds a delete marker. A buggy script or a stolen key with the policy above can hide backups but not destroy them. Each version is billed as a full object, and versioning can later be suspended but never removed, so pair it with the noncurrent-version rule below.
Expire old backups with a lifecycle rule
{
"Rules": [
{
"ID": "expire-server-backups",
"Filter": { "Prefix": "" },
"Status": "Enabled",
"Expiration": { "Days": 30 },
"NoncurrentVersionExpiration": { "NoncurrentDays": 7 },
"AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
}
]
}"Prefix": ""applies the rule to the whole bucket. Useweb-01/to target one server.ExpirationDays: 30: in a versioned bucket, S3 adds a delete marker 30 days after upload, and the backup becomes noncurrent.NoncurrentVersionExpirationNoncurrentDays: 7: permanently deletes a version 7 days after it became noncurrent. A backup lives about 37 days, and an accidental delete can be undone for 7. AddNewerNoncurrentVersions(1 to 100) to also keep that many newer old versions.AbortIncompleteMultipartUpload: removes the parts of uploads that never finished. You pay for those parts until they go.- With
Daysset, S3 also removes leftover delete markers on its own.
aws s3api put-bucket-lifecycle-configuration --bucket acme-server-backups --lifecycle-configuration file://lifecycle.jsonThis command replaces the bucket's entire lifecycle configuration, so the file must contain every rule you want. Rules also apply to objects already in the bucket: a new 30-day rule queues everything older than 30 days for removal.
Removal is asynchronous, but storage isn't billed after the expiration date. Run this with admin credentials; the backup user can't change lifecycle rules, by design.
Choose a storage class
| Class (API value) | Minimum duration | Minimum billable size | Getting data back |
|---|---|---|---|
S3 Standard (STANDARD) | None | None | Immediate, no retrieval fee |
Standard-IA (STANDARD_IA) | 30 days | 128 KB | Immediate, per-GB retrieval fee |
One Zone-IA (ONEZONE_IA) | 30 days | 128 KB | Immediate, per-GB fee, one Availability Zone |
Glacier Instant Retrieval (GLACIER_IR) | 90 days | 128 KB | Milliseconds, per-GB fee |
Glacier Flexible Retrieval (GLACIER) | 90 days | 40 KB metadata added per object | Restore first: minutes to hours |
Glacier Deep Archive (DEEP_ARCHIVE) | 180 days | 40 KB metadata added per object | Restore first: hours |
Deleting an object before its minimum duration bills the rest of it, so a 7-day backup in Standard-IA is charged for 30 days. Use Standard for short retention and Standard-IA for copies kept 30 days or more and rarely restored. Glacier Flexible Retrieval and Deep Archive need a restore request before download, which slows a recovery. AWS recommends One Zone-IA only for data you can recreate.
aws s3 cp web-01-2026-10-03.tar.gz s3://acme-server-backups/web-01/2026-10-03.tar.gz --storage-class STANDARD_IA --profile backupsLock backups with Object Lock
Object Lock stops object versions from being deleted or overwritten until a date. It needs versioning, and you can turn it on when creating the bucket or later from the bucket's Properties tab. After that, Object Lock can't be disabled and versioning can't be suspended.
- Governance mode: only a user with
s3:BypassGovernanceRetentionwho sends the bypass header can delete locked versions. Enough to stop a stolen server key. - Compliance mode: nobody can delete a locked version or shorten its lock, root included. AWS says the only early exit is deleting the account.
- Legal hold: a lock with no end date, removed by hand.
aws s3api put-object-lock-configuration --bucket acme-server-backups --object-lock-configuration '{"ObjectLockEnabled": "Enabled", "Rule": {"DefaultRetention": {"Mode": "GOVERNANCE", "Days": 14}}}'This sets a default retention, so every new version is locked for 14 days without changing your upload commands. Lifecycle rules don't remove noncurrent versions that have Object Lock applied, so keep the lock shorter than your expiration window.
Start with governance mode and a short period. A compliance-mode lock set too long can't be undone, and you pay for that storage until it ends.
Upload backups with the AWS CLI
Give every backup a unique, dated key so nothing overwrites it. Make the archive first, for example with tar, then copy it:
aws s3 cp /var/backups/web-01-2026-10-03.tar.gz s3://acme-server-backups/web-01/2026-10-03.tar.gz --profile backups --only-show-errors--only-show-errors keeps cron logs short. A single PUT tops out at 5 GB; the aws s3 commands switch to multipart for large files, up to 48.8 TiB per object. To stream an archive without a temporary file, use - as the source:
tar -czf - /etc /var/www | aws s3 cp - s3://acme-server-backups/web-01/2026-10-03-files.tar.gz --profile backupsFor a stream over 50 GB, add --expected-size with the size in bytes, or the upload can fail at the 10,000-part limit. To mirror a local backup directory instead:
aws s3 sync /var/backups/web-01/ s3://acme-server-backups/web-01/ --profile backups --only-show-errorssync copies files that are new, changed in size, or newer locally. It deletes nothing unless you add --delete; leave that off, or a wiped local directory empties the remote copy too. Schedule either command with cron.
Verify a backup
aws s3 ls s3://acme-server-backups/web-01/ --recursive --human-readable --summarize --profile backupsaws s3api head-object --bucket acme-server-backups --key web-01/2026-10-03.tar.gz --checksum-mode ENABLED --profile backupshead-object returns ContentLength and, with --checksum-mode ENABLED, the stored checksum. S3 adds a CRC-64/NVME checksum when an upload doesn't specify one. Don't compare the ETag with an MD5 sum: for multipart uploads it isn't one. The real proof is a download:
aws s3 cp s3://acme-server-backups/web-01/2026-10-03.tar.gz /tmp/restore-test.tar.gz --profile backups && sha256sum /tmp/restore-test.tar.gz && tar -tzf /tmp/restore-test.tar.gz > /dev/null && echo OKCompare the hash with one recorded before upload. To recover an overwritten or deleted backup, find it with aws s3api list-object-versions --bucket acme-server-backups --prefix web-01/ and fetch it with aws s3api get-object and --version-id. Listing versions needs s3:ListBucketVersions, so use admin credentials. More in testing a restore.
What it costs
Rates vary by Region and class; check the S3 pricing page for current numbers. As of October 2026, you pay for:
- Storage per GB-month, by storage class and Region. Every version and every unfinished multipart part counts.
- Requests, priced by type. DELETE and CANCEL requests are free.
- Retrieval fees per GB for the IA and Glacier classes, plus minimum-duration charges.
- Data in from the internet is free. Data out to the internet is billed per GB, after the first 100 GB a month, which is shared across all AWS services.
Egress is the cost people forget: a full restore to a server outside AWS pays per GB out.
Limits and gotchas
- An account can create 10,000 general purpose buckets by default.
- Multipart parts are 5 MiB to 5 GiB, with at most 10,000 parts per upload.
- Lifecycle transitions skip objects under 128 KB by default.
- An admin in the same AWS account can delete any version not under a compliance-mode lock. For a copy that survives an account takeover, keep one in a separate account or at a second provider, per the 3-2-1 rule.
A key that can upload can usually delete too. For backups an attacker cannot remove, see protecting backups from ransomware; to keep what is stored readable only by you, encrypt it before upload.
Frequently asked questions
- Is a new S3 bucket private by default?
- Yes. New buckets have all four Block Public Access settings on and ACLs disabled, so nothing is public unless you change those settings.
- Do I need to turn on encryption for an S3 backup bucket?
- No. Since January 5, 2023, S3 encrypts all new uploads with SSE-S3 at no cost. Choose SSE-KMS only if you need to control the keys, and expect KMS charges.
- Which S3 storage class is best for backups?
- Standard for backups kept less than 30 days. Standard-IA for copies kept 30 days or more and rarely restored, Glacier Instant Retrieval for 90 days or more. Glacier Flexible Retrieval or Deep Archive only for archives you can wait hours to restore.
- Can I enable S3 Object Lock on an existing bucket?
- Yes. It requires versioning, and once enabled, Object Lock can't be disabled and versioning can't be suspended.
- Does S3 versioning cost extra?
- Each version is billed as a full object. A
NoncurrentVersionExpirationlifecycle rule deletes old versions after a set number of days.
How this was checked
Commands, limits and prices were checked against these official pages, on October 3, 2026:
- Amazon S3 User Guide: Creating a general purpose bucket
- Amazon S3 User Guide: Blocking public access to your Amazon S3 storage
- Amazon S3 User Guide: Configuring default encryption
- Amazon S3 User Guide: Identity-based policy examples
- Amazon S3 User Guide: Policy examples using condition keys (s3:prefix)
- IAM User Guide: Manage access keys for IAM users
- IAM User Guide: How an IAM administrator can manage IAM user access keys
- IAM User Guide: Programmatic access and alternatives to long-term access keys
- Amazon S3 User Guide: Retaining multiple versions of objects with S3 Versioning
- Amazon S3 User Guide: Enabling versioning on buckets
- Amazon S3 User Guide: Examples of S3 Lifecycle configurations
- Amazon S3 User Guide: Expiring objects
- Amazon S3 User Guide: Transitioning objects using Amazon S3 Lifecycle
- Amazon S3 User Guide: Understanding and managing Amazon S3 storage classes
- Amazon S3 User Guide: Locking objects with Object Lock
- Amazon S3 User Guide: Configuring S3 Object Lock
- Amazon S3 User Guide: Uploading and copying objects using multipart upload
- Amazon S3 User Guide: Amazon S3 multipart upload limits
- Amazon S3 User Guide: Checking object integrity for data uploads
- AWS CLI reference: s3 cp
- AWS CLI reference: s3 sync
- AWS CLI reference: s3 ls
- AWS CLI reference: s3api create-bucket
- AWS CLI reference: s3api put-bucket-lifecycle-configuration
- AWS CLI reference: s3api head-object
- AWS CLI reference: s3api get-public-access-block
- AWS CLI reference: s3api list-object-versions
- Amazon S3 pricing