Auto-Deleting Old S3 Objects: How to Set Up a Lifecycle Rule for 30-Day Expiration
Auto-deleting old S3 objects with a Lifecycle Rule is the standard way to enforce 30-day retention and eliminate storage costs from accumulating stale data.
TL;DR: S3 Lifecycle Expiration in 60 Seconds
| Step | Action | Where |
|---|---|---|
| 1 | Open bucket → Management → Lifecycle rules | S3 Console |
| 2 | Create rule with Expiration: 30 days | Console or CLI |
| 3 | Optionally scope to a prefix or tag | Rule filter |
| 4 | Verify rule is Enabled and confirm no conflicting rules | Console / CLI |
Why Auto-Deleting Old S3 Objects Matters for Cost Control
S3 charges for every GB stored, every day. Without an expiration policy, buckets silently accumulate objects — log files, temporary uploads, pipeline artifacts — long after they serve any purpose. A single misconfigured ETL job writing 10 GB/day produces 300 GB of orphaned data in a month. A Lifecycle Rule (S3's built-in object expiration mechanism) removes objects automatically on a schedule you define, with no Lambda, no cron job, and no per-deletion API cost.
Think of a Lifecycle Rule like a lease agreement on a storage unit: when the lease expires, the contents are cleared automatically — you don't need to show up in person.
How S3 Lifecycle Expiration Works
S3 evaluates Lifecycle rules daily. When an object's age (measured from its Last-Modified date) meets or exceeds the configured expiration threshold, S3 schedules it for deletion. The deletion itself may occur up to 24 hours after the threshold is crossed — this is expected behavior, not a bug.
- Object Created: S3 records the
Last-Modifiedtimestamp at upload time. - Daily Evaluation: S3's internal Lifecycle engine scans objects against all enabled rules for the bucket.
- Age Check: If
current_date − Last-Modified ≥ 30 days, the object is marked for expiration. - Deletion Executed: S3 permanently deletes the object (or, for versioned buckets, inserts a delete marker — see the versioning note below).
- Storage Billing Stops: The object no longer appears in storage metrics or billing after deletion.
Versioning Changes the Deletion Model — Read This First
This is the most common production gotcha with S3 Lifecycle rules.
Symptom: You configure a 30-day expiration rule, but your bucket storage keeps growing and objects remain visible in the console.
Misdiagnosis: Engineers assume the rule isn't working or wasn't saved correctly.
Actual cause: Bucket versioning is enabled. On a versioned bucket, an expiration action on current versions inserts a delete marker rather than permanently removing the object. The non-current versions and the delete markers themselves continue to consume storage until separate rule actions target them explicitly.
For versioned buckets, you need three coordinated actions in your Lifecycle rule:
Expiration→ expires current object versions (inserts delete marker)NoncurrentVersionExpiration→ permanently deletes non-current versions after N daysExpiredObjectDeleteMarker→ cleans up orphaned delete markers
- Unversioned path (left): Expiration directly and permanently deletes the object.
- Versioned path (right): Expiration on the current version inserts a delete marker. The previous version becomes non-current and persists until
NoncurrentVersionExpirationfires. - Delete marker cleanup: Once all non-current versions are gone,
ExpiredObjectDeleteMarker: trueremoves the orphaned marker, fully freeing storage.
Setting Up the Lifecycle Rule: Two Approaches
Choose based on your workflow. The AWS CLI approach is repeatable and version-controllable; the console is faster for one-off configurations.
Approach A (Recommended): AWS CLI — Repeatable and Scriptable
The CLI accepts a JSON configuration file, making the rule auditable and deployable via CI/CD pipelines. The example below covers both unversioned and versioned bucket scenarios in a single rule.
First, create the Lifecycle configuration JSON file:
🔽 lifecycle-config.json — Click to expand
{
"Rules": [
{
"ID": "delete-objects-older-than-30-days",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"Days": 30
},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
}
]
}
Apply the configuration to your bucket:
aws s3api put-bucket-lifecycle-configuration \
--bucket YOUR-BUCKET-NAME \
--lifecycle-configuration file://lifecycle-config.json
Verify the rule was applied correctly — this step catches silent save failures that the console sometimes masks:
aws s3api get-bucket-lifecycle-configuration \
--bucket YOUR-BUCKET-NAME
Expected output includes your rule with "Status": "Enabled". If the command returns a NoSuchLifecycleConfiguration error, the rule was not saved.
To add delete marker cleanup for versioned buckets, update the rule to include the Expiration.ExpiredObjectDeleteMarker flag. Note that ExpiredObjectDeleteMarker and Days under Expiration are mutually exclusive — use separate rules if you need both behaviors:
🔽 lifecycle-config-versioned.json — Click to expand
{
"Rules": [
{
"ID": "expire-current-versions-30-days",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"Days": 30
},
"NoncurrentVersionExpiration": {
"NoncurrentDays": 30
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
},
{
"ID": "cleanup-expired-delete-markers",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"Expiration": {
"ExpiredObjectDeleteMarker": true
}
}
]
}
Approach B: AWS Console — Fast for One-Off Rules
- Navigate to S3 → your bucket → Management → Lifecycle rules → Create lifecycle rule.
- Enter a rule name (e.g.,
delete-objects-older-than-30-days). - Under Filter type, leave the prefix blank to apply to all objects, or enter a prefix (e.g.,
logs/) to scope the rule. - Under Lifecycle rule actions, check Expire current versions of objects.
- Set Days after object creation to
30. - If versioning is enabled, also check Permanently delete noncurrent versions of objects and set the days value.
- Review and save. Confirm the rule shows Enabled in the rules list.
Scoping Rules to a Prefix or Tag
Applying a rule to the entire bucket is appropriate for single-purpose buckets. For shared buckets, scope the rule using a prefix or object tag to avoid unintended deletions.
Prefix-scoped example — targets only objects under tmp/uploads/:
{
"Rules": [
{
"ID": "expire-tmp-uploads-30-days",
"Status": "Enabled",
"Filter": {
"Prefix": "tmp/uploads/"
},
"Expiration": {
"Days": 30
}
}
]
}
Tag-scoped example — targets objects tagged retention=short:
{
"Rules": [
{
"ID": "expire-short-retention-30-days",
"Status": "Enabled",
"Filter": {
"Tag": {
"Key": "retention",
"Value": "short"
}
},
"Expiration": {
"Days": 30
}
}
]
}
Tag-based filtering requires that objects are tagged at upload time. If tags are missing, the rule silently skips those objects — there is no error or warning.
IAM Permissions Required
The IAM principal applying the Lifecycle rule needs the following permissions. Note that s3:GetLifecycleConfiguration and s3:PutLifecycleConfiguration operate at the bucket level, not the object level.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetLifecycleConfiguration",
"s3:PutLifecycleConfiguration"
],
"Resource": "arn:aws:s3:::YOUR-BUCKET-NAME"
}
]
}
S3 itself performs the actual object deletions using its internal service principal — your IAM policy does not need s3:DeleteObject for the Lifecycle engine to function.
Diagnosing a Lifecycle Rule That Appears Not to Fire
In practice, teams often assume a rule is broken when objects persist past day 30. Before re-creating the rule, work through these checks in order — each one catches what the previous step cannot.
-
Confirm the rule status is
Enabled— a rule saved asDisablednever evaluates, and the console does not warn you at creation time.aws s3api get-bucket-lifecycle-configuration \ --bucket YOUR-BUCKET-NAME \ --query 'Rules[*].{ID:ID,Status:Status}' -
Check whether versioning is enabled on the bucket — if it is, current-version expiration inserts delete markers rather than permanently deleting objects, which is why storage appears unchanged even after the rule fires.
If the output showsaws s3api get-bucket-versioning \ --bucket YOUR-BUCKET-NAME"Status": "Enabled", addNoncurrentVersionExpirationand the delete marker cleanup rule as shown in the versioned config above. -
Verify the object's
Last-Modifieddate — Lifecycle age is calculated fromLast-Modified, not from any application-level timestamp. An object copied or re-uploaded resets this clock.
Inspect theaws s3api head-object \ --bucket YOUR-BUCKET-NAME \ --key path/to/your/object.logLastModifiedfield in the response. -
Check for an S3 Object Lock on the bucket — Object Lock (a WORM compliance mechanism) can prevent Lifecycle deletions from completing. If Object Lock is active, Lifecycle expiration may be blocked for locked objects.
Ifaws s3api get-object-lock-configuration \ --bucket YOUR-BUCKET-NAMEObjectLockEnabledisEnabled, review the retention mode and period configured on individual objects.
Lifecycle evaluation happens once per day — a rule applied today will not retroactively delete objects that crossed the 30-day threshold yesterday until the next evaluation cycle runs.
Cost Impact and the AbortIncompleteMultipartUpload Bonus
Object expiration eliminates storage charges for deleted objects. One frequently overlooked cost source is incomplete multipart uploads: parts uploaded but never completed or aborted accumulate storage charges indefinitely and are invisible in standard S3 object listings. The AbortIncompleteMultipartUpload action in the example configurations above cleans these up automatically after 7 days.
Pricing and storage class rates vary — always verify current costs in the AWS S3 Pricing page.
- Standard objects: Deleted at day 30 by the
Expirationaction. - Incomplete multipart uploads: Aborted at day 7 by
AbortIncompleteMultipartUpload, preventing silent storage accumulation. - Non-current versions (versioned buckets): Permanently deleted at day 30 after becoming non-current via
NoncurrentVersionExpiration.
Wrap-Up: Auto-Deleting Old S3 Objects Without Surprises
A single S3 Lifecycle Rule handles 30-day object expiration with no ongoing operational overhead. The two decisions that determine whether it works correctly in production are: (1) whether versioning is enabled, and (2) whether the rule filter matches the objects you intend to target. Verify both before declaring the rule complete.
For further reading, see the AWS S3 Object Lifecycle Management documentation and the Lifecycle configuration examples.
Glossary
| Term | Definition |
|---|---|
| Lifecycle Rule | An S3 bucket-level policy that automates transitions and expirations of objects based on age or other criteria. |
| Expiration Action | A Lifecycle rule action that permanently deletes an object (or inserts a delete marker on versioned buckets) after a specified number of days. |
| Delete Marker | A placeholder inserted by S3 when a versioned object is "deleted"; the object's previous versions remain until explicitly removed. |
| NoncurrentVersionExpiration | A Lifecycle action that permanently deletes non-current (previous) versions of objects in a versioned bucket after a specified number of days. |
| AbortIncompleteMultipartUpload | A Lifecycle action that cancels and removes multipart upload parts that were never completed, preventing silent storage cost accumulation. |
Comments
Post a Comment