If you pay for Amazon S3 (Simple Storage Service, the object storage in Amazon Web Services), four lifecycle rules deliver most of the savings. They shrink the bill without deleting data you still need. A lifecycle rule is a bucket setting that tells S3 to delete or move objects once they reach a certain age. The first rule expires old log files. The second removes noncurrent versions, which are the older copies S3 keeps when versioning is on and a file gets overwritten or deleted. The third aborts incomplete multipart uploads after seven days; these are large uploads sent in pieces that never finished but still take up paid storage. The fourth moves large, rarely read objects to a cheaper storage class, with a size filter so small files stay where they are. Rules run once a day and cost nothing to set up. Trouble starts when a rule moves lots of small objects, clashes with versioning, or deletes something a restore process still needed.
What’s happening
A lifecycle configuration is a set of rules attached to a bucket. Each rule has a filter (prefix, tags, object size, or a combination) and one or more actions: Transition moves current versions to another storage class after N days, Expiration deletes them, NoncurrentVersionTransition and NoncurrentVersionExpiration do the same for old versions in a versioned bucket, and AbortIncompleteMultipartUpload discards stalled upload parts. S3 evaluates rules roughly once a day, asynchronously — actions can lag the eligibility date, but billing stops at eligibility for expirations, so the lag costs you nothing.
The cost mechanics worth knowing: transitions are billed per 1,000 requests, and transitions into Glacier classes cost more per request than transitions into IA. That means transitioning millions of tiny objects can cost more in request fees than the class change saves — which is why the ObjectSizeGreaterThan filter exists and why lifecycle now skips transitions of objects under 128 KB by default. Minimum-duration rules still apply after a transition: an object moved to Standard-IA and deleted 10 days later still bills the 30-day minimum. The full action reference is in the S3 documentation.
What to check first
- Is versioning on? If yes, expiring a current version only adds a delete marker — the bytes live on as noncurrent versions until a noncurrent-version rule removes them. This is the most common “the rule did nothing” report.
- What already exists. Buckets accumulate rules from different eras; conflicting or overlapping rules are common.
- Who reads old data. A rule that expires “old exports” has broken more than one quarterly reporting job.
- Object size distribution. Decides whether transitions save or cost money.
How to verify
Dump the current configuration and read it before touching anything:
aws s3api get-bucket-lifecycle-configuration --bucket my-bucket
# Versioning state — changes what expiration actually does
aws s3api get-bucket-versioning --bucket my-bucket
# How much is hiding in noncurrent versions and incomplete uploads?
# Storage Lens: S3 > Storage Lens > Dashboards > your dashboard
# (see the noncurrent version bytes and incomplete MPU bytes metrics)
In the console, S3 > your bucket > Management > Lifecycle rules shows each rule’s filter and actions. After a rule takes effect, confirm movement with the per-class storage metrics under S3 > your bucket > Metrics — the Standard line should fall as the target class rises.
The runbook
-
Abort incomplete multipart uploads everywhere. Zero risk, immediate savings on buckets with upload traffic — details in multipart upload charges and cleanup. This rule belongs on every bucket you own.
-
Expire data with a known end of life. Logs, temp exports, build artifacts. Confirm the retention requirement in writing first.
-
Cap noncurrent versions on versioned buckets. Keep what rollback actually needs — a count and an age — and expire the rest. Add
ExpiredObjectDeleteMarkercleanup so delete markers do not accumulate. -
Transition large cold objects with a size filter. A working example combining all four patterns:
{ "Rules": [ { "ID": "abort-incomplete-mpu", "Status": "Enabled", "Filter": {}, "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 } }, { "ID": "expire-logs", "Status": "Enabled", "Filter": { "Prefix": "logs/" }, "Expiration": { "Days": 90 } }, { "ID": "cap-noncurrent-versions", "Status": "Enabled", "Filter": {}, "NoncurrentVersionExpiration": { "NoncurrentDays": 30, "NewerNoncurrentVersions": 3 }, "Expiration": { "ExpiredObjectDeleteMarker": true } }, { "ID": "archive-large-exports", "Status": "Enabled", "Filter": { "And": { "Prefix": "exports/", "ObjectSizeGreaterThan": 131072 } }, "Transitions": [ { "Days": 90, "StorageClass": "GLACIER_IR" } ] } ] }Apply it with:
aws s3api put-bucket-lifecycle-configuration \ --bucket my-bucket \ --lifecycle-configuration file://lifecycle.jsonNote that
put-bucket-lifecycle-configurationreplaces the whole configuration — merge with the existing rules first, never write over them blind. -
Wait a cycle, then measure. Rules can take up to 48 hours to start acting. Check the class metrics and the next invoice’s usage lines before declaring victory.
When this is the wrong move
Lifecycle rules are the wrong tool when the access pattern is unknown — that is Intelligent-Tiering’s territory. They are the wrong tool for one-off cleanups of a specific object list — that is S3 Batch Operations. And they are dangerous on buckets that back replication or cross-region backup flows without checking what the downstream side expects to still exist.
Common mistakes
- Expiring current versions on a versioned bucket and wondering why the bill did not move — the bytes are now noncurrent versions plus delete markers.
- Transitioning millions of small objects to Glacier and paying more in transition requests and per-object overhead than the class change saves.
- Overwriting an existing lifecycle configuration with
put-bucket-lifecycle-configurationand silently deleting someone else’s rules. - Setting a transition to Standard-IA before day 30 — S3 rejects transition timing that violates class constraints, and people burn time on the validation error.
Prevention
- Keep lifecycle configurations in Terraform or CloudFormation so changes are reviewed and the “replace, not merge” behaviour cannot ambush anyone.
- Put the abort-incomplete-MPU rule in your bucket module so every new bucket gets it by default.
- Document retention per prefix in the repo next to the rule — the rule is the enforcement, not the record.
- Review rules against Storage Lens quarterly: rules that no longer match how the bucket is used are either dead weight or a slow leak.
Lifecycle rules are the rare cost tool that keeps working after everyone stops paying attention — as long as the versioning interaction was understood on day one. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.