S3 Lifecycle Rule Not Deleting Objects: Midnight UTC Timing, Versioned Buckets, the Rule That Vanished, and How to Prove It Ran

Logeshwaran
—

An S3 lifecycle rule that seems to do nothing is usually working on S3's clock, not yours. S3 checks lifecycle rules once a day, at midnight UTC, rounds every expiry date up to the next midnight UTC, and then removes objects in the background, which can take days in a big bucket. The good news is that billing stops on the eligibility date, even while the objects still show up in listings. When a rule really is broken, the cause is almost always one of four things. The bucket has versioning on, so "expire" only hides objects behind delete markers, so a 100-object bucket can grow to 200 objects after your rule runs. The filter matches nothing, often because of a leading slash or a tag that is not exactly right. Hidden incomplete multipart uploads keep the bill up. Or the rule is simply gone: the surprise most people learn the hard way is that put-bucket-lifecycle-configuration replaces every existing rule on the bucket, so adding one rule from the command line quietly deletes all the others.

Jake's repair shop keeps the counter camera's footage in an S3 bucket, because a customer once disputed whether a phone was cracked when it came in. Ethan set a lifecycle rule to delete clips after 30 days, so the bill would stay flat. Three months later the bill had tripled. Jake opened the S3 console, saw the 30-day rule sitting there, "Enabled," and asked Ethan the obvious question: "Then why is it still growing?" Ethan found two separate problems in ten minutes, and neither was the rule's fault. This page walks through how lifecycle timing really works, why versioned buckets keep growing, the overwrite trap, filters that match nothing, the hidden uploads that never expire, transitions that cost more than they save, and how to prove a rule actually ran.

⚡ Quick Answer

• New rule, nothing happened yet? → Normal. Rules run once a day at 00:00 UTC, dates round up to the next midnight UTC, and removal is asynchronous. The timing.

• Versioned bucket keeps growing? → Expiration only adds delete markers. Add noncurrent-version expiration and expired-marker cleanup. Fix.

• Rules disappeared? → put-bucket-lifecycle-configuration replaces the whole configuration. Get, edit, then put. Safe edit.

• Rule matches nothing? → Check the prefix (no leading slash) and exact tags. Filters.

• Did it run? → aws s3api head-object shows each object's expiry date and rule ID. Proof.

Everything on this page reads settings until you choose to change a rule. Lifecycle deletions can't be undone without versioning.

New to S3? Our plain-English guide to what Amazon S3 is explains buckets and objects from zero, and our guide to S3 storage classes explains what a "transition" moves objects between.

Lifecycle runs on S3's clock, not yours

Your lifecycle rule is not a timer that fires the moment an object turns 30 days old. Here is what really happens to your objects:

  • Age is counted from the object's creation time, in UTC. For S3, "creation date" means the last-modified date: replacing an object starts its clock again.
  • The due date is rounded up to the next midnight UTC. S3 adds the rule's number of days to the creation time and rounds the result to midnight UTC the following day.
  • S3 evaluates rules once a day, at 00:00 UTC, and queues every eligible object.
  • The actual removal or transition is asynchronous. There can be a delay between the eligibility date and the moment the object really disappears, longer when a bucket has millions of objects or when the whole Region is busy.

Here is how that plays out for you, step by step. Say your bucket has a rule to expire objects after one day, and you upload an object on January 1 at 17:05 Pacific Daylight Time, which is January 2 at 00:05 UTC. It becomes one day old at 00:05 UTC on January 3, and so it is eligible when lifecycle next evaluates, at 00:00 UTC on January 4. A "one day" rule acted on day three. Add the background delay for a large bucket, and seeing that object in a listing on January 5 is still normal.

Two things should make that delay much less alarming for you:

  • Your bill changes on the eligibility date. You are not charged for an expired object's storage after it becomes eligible, even if it still appears in listings for a while. The one exception is transitions to S3 Intelligent-Tiering, which are billed at the new rate only once the object has actually moved.
  • Rules apply to objects that already exist. A new 30-day rule queues every existing object older than 30 days on its first run, not just new uploads. In a bucket with years of data, that first run can take several days to work through.

Jake: "So it's like the council's bin collection. Putting the bin out doesn't mean the truck comes right then."

Ethan: "Exactly, and the council stops charging you for the rubbish the moment it's at the curb, even if the truck comes on Thursday. If the bin is still full a week later, that's when you look for a real problem."

The five-minute diagnosis, in order

Before you change anything, run these checks in this order. Each one rules out a cause, and together they find almost every broken rule:

  1. Confirm your rules still exist. aws s3api get-bucket-lifecycle-configuration --bucket NAME prints every rule. If yours is missing, jump to the overwrite trap. If it says "Status": "Disabled", it never runs.
  2. Check whether your bucket is versioned. aws s3api get-bucket-versioning --bucket NAME. If it says Enabled or Suspended, expiration only hides objects; read the versioning section.
  3. Pick one object you expected to be gone and ask S3 about it with head-object. If the response has no expiration date, your filter does not match it. If the date has passed, it is queued and you are no longer paying for it.
  4. Test your prefix with list-objects-v2 --prefix, exactly as written in the rule. No results means a typo, a leading slash or a case difference.
  5. Look for hidden uploads with list-multipart-uploads. Old entries are storage you pay for that no expiration rule will ever touch.
  6. Count the days in UTC, from the object's last-modified time, and round up to the next midnight UTC. If that moment has not passed, your rule has not had its chance yet.

The sections below explain each finding and its fix, in the same order you are likely to need them.

Versioning: the rule "worked," but the bucket kept growing

This is the most common real cause, and it is what had happened to Jake's camera bucket. Ethan had switched on versioning months earlier, to protect the warranty photos, and the footage bucket had inherited the same setup.

In a bucket with versioning enabled or suspended, an Expiration action does not delete anything. It adds a delete marker on top of each object, which turns the object's data into a noncurrent version that stays stored and billed. Delete markers are counted as objects too. Picture your versioned bucket with 100 objects and a rule that expires current versions after 7 days: after day seven you have 200 objects, the 100 originals, now noncurrent, plus 100 delete markers. Your rule did exactly what it says; it just did not do what you expected.

Three more versioning details explain the rest of the "nothing happens" reports:

  • Expiration applies only to the current version. It never touches noncurrent versions.
  • If an object's current version is already a delete marker and older versions exist, Expiration does nothing at all.
  • Objects protected by S3 Object Lock get a delete marker instead of being deleted, and their noncurrent versions are kept, not expired. Objects whose replication status is PENDING or FAILED are skipped entirely until replication settles.

The rule set that really empties a versioned bucket

To make old data actually disappear from a versioned bucket, you need four actions working together, the same combination you would use to empty a whole bucket with lifecycle:

ActionConsole nameWhat it does
Expiration with DaysExpire current versions of objectsAdds a delete marker to objects older than the given days
NoncurrentVersionExpirationPermanently delete noncurrent versions of objectsPermanently deletes data a set number of days after it became noncurrent
ExpiredObjectDeleteMarkerDelete expired object delete markersRemoves delete markers that no longer have any version behind them
AbortIncompleteMultipartUploadDelete incomplete multipart uploadsCleans up uploads that started and never finished

As a command, for camera footage kept 30 days plus a 7-day safety window for anything overwritten or deleted. Expiration cannot hold both a number of days and the expired-marker setting at once, which is why that cleanup gets its own small rule:

aws s3api put-bucket-lifecycle-configuration --bucket jakes-camera --lifecycle-configuration '{
  "Rules": [
    {
      "ID": "footage-30-days",
      "Status": "Enabled",
      "Filter": {"Prefix": "footage/"},
      "Expiration": {"Days": 30},
      "NoncurrentVersionExpiration": {"NoncurrentDays": 7},
      "AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}
    },
    {
      "ID": "clean-expired-markers",
      "Status": "Enabled",
      "Filter": {"Prefix": "footage/"},
      "Expiration": {"ExpiredObjectDeleteMarker": true}
    }
  ]
}'

Read the next section before you run this on a bucket that already has rules: this command replaces them.

The NoncurrentVersionExpiration line is the one that turns "hidden" into "gone," and it is permanent: data it removes cannot be recovered. Choose its days as the length of time you want to be able to undo a mistake. If you need to undo one inside that window, our guide to restoring deleted S3 files shows how to remove the delete markers.

The rule that vanished: put-bucket-lifecycle-configuration replaces everything

This was Ethan's second discovery. A colleague had added a 7-day rule for a logs/ folder from the command line, with put-bucket-lifecycle-configuration and a configuration containing just that one rule. The footage rule Jake saw in the console was gone two weeks later, when the colleague added the logs rule; Jake had been looking at an old browser tab.

The API works this way on purpose. PutBucketLifecycleConfiguration creates a new lifecycle configuration or replaces the existing one; to keep any existing rules, you must include them in the configuration you send. There is no "add one rule" call. In the console you edit rules one at a time, but the CLI, SDKs and many scripts send exactly the list you give them, and that list becomes the whole configuration.

The safe way to add or change a rule from the command line is always get, edit, put:

# 1. Save the current configuration (keep this file as your backup)
aws s3api get-bucket-lifecycle-configuration --bucket jakes-camera > lifecycle.json

# 2. Keep only the rules, and add the new one to the list
jq '{Rules: (.Rules + [{"ID": "logs-7-days", "Status": "Enabled", "Filter": {"Prefix": "logs/"}, "Expiration": {"Days": 7}}])}' \
  lifecycle.json > lifecycle-new.json

# 3. Check the result, then write all the rules back
cat lifecycle-new.json
aws s3api put-bucket-lifecycle-configuration --bucket jakes-camera --lifecycle-configuration file://lifecycle-new.json

Step 2 builds a new file with only the Rules list for a reason. The saved configuration also contains a TransitionDefaultMinimumObjectSize value, and handing the whole file back to put-bucket-lifecycle-configuration fails with Unknown parameter in LifecycleConfiguration: "TransitionDefaultMinimumObjectSize", must be one of: Rules. That setting travels separately, as the --transition-default-minimum-object-size option. If your saved file shows varies_by_storage_class there, add --transition-default-minimum-object-size varies_by_storage_class to the put command to keep it; all_storage_classes_128K is the default.

If the first command answers NoSuchLifecycleConfiguration, the bucket has no rules yet and you can put a new configuration directly. If your bucket's rules are managed by CloudFormation, CDK or Terraform, make every change there instead; a change made by hand is overwritten the next time the template is deployed, which is another way rules "vanish."

Jake: "So the colleague didn't add a line to the rules sheet. He replaced the whole sheet with a page that had one line on it."

Ethan: "Right. And the console kept showing me the old sheet because I hadn't refreshed. Always reload the rules before trusting them."

Filters that match nothing

Your rule only acts on objects its filter selects, and filters are exact. These are the mistakes that make your rule apply to nothing:

  • A leading slash. S3 object names do not start with /. A prefix of /footage/ matches nothing in a bucket whose objects are named footage/2026/clip.mp4. Use footage/.
  • Case. Prefixes are case-sensitive: Footage/ and footage/ are different.
  • A missing trailing slash, or an extra one. footage also matches footage-old/ and footage.zip; footage/ matches only that folder. Sometimes the wider match is what you want, as the next section shows.
  • Tags that are almost right. A tag filter must match the tag key and value exactly. Several tags in one filter must all be present on the object. A tag filter with a key and no value matches only objects whose tag has no value.
  • Size limits. ObjectSizeGreaterThan and ObjectSizeLessThan filter by bytes, and a range excludes objects outside it.
  • The rule is disabled. "Status": "Disabled" keeps a rule in the configuration without running it.

You can check your prefix against real object names in one command; if this lists nothing for you, your rule will not find anything either:

aws s3api list-objects-v2 --bucket jakes-camera --prefix footage/ --max-items 5 \
  --query 'Contents[].[Key,LastModified]' --output text

Excluding a folder, or covering several

Lifecycle filters cannot exclude a prefix, and a rule cannot list several prefixes. Two workarounds cover nearly every case:

  • Tag what you want included. Give objects a tag such as retention=30d when they are uploaded, and filter the rule on that tag. Everything without it, including the folder you wanted to exclude, is left alone.
  • Use a shorter shared prefix. Folders that start with the same letters can be covered by a prefix with no trailing slash. sales1999/, sales2000/ and sales2001/ are all matched by sales.
  • Otherwise, write one rule per prefix. A configuration can hold many rules.

Incomplete multipart uploads: the storage nobody can see

Large files are uploaded to S3 in parts. If an upload starts and never finishes, because a laptop slept or a script crashed, the parts already sent stay in the bucket and are billed, but they are not objects: they do not show up in listings, and Expiration rules do not touch them. A bucket can look small in the console and still carry gigabytes of abandoned parts.

List them like this:

aws s3api list-multipart-uploads --bucket jakes-camera \
  --query 'Uploads[].[Key,Initiated]' --output text

An empty result means none. Anything listed with an old date is waste. The fix is the AbortIncompleteMultipartUpload action shown earlier, which removes uploads a set number of days after they started; 7 days is a common choice, long enough for any genuine slow upload to finish. Our guide to S3 EntityTooLarge explains when uploads switch to multipart in the first place.

Transitions that never happen, or cost more than they save

If your rules move objects to a cheaper storage class, you will meet a few surprises of your own:

  • Small objects do not move. By default, objects smaller than 128 KB are not transitioned to any storage class. A rule that "isn't working" on a bucket of tiny files is behaving correctly.
  • Archive classes add a 40 KB overhead per object. Each object moved to S3 Glacier Flexible Retrieval or S3 Glacier Deep Archive carries 40 KB of extra billed storage: 8 KB at S3 Standard rates and 32 KB at the archive rate. For millions of small files, that overhead can cost more than the move saves.
  • Every transition is a request, and requests are billed. In US East (N. Virginia), lifecycle transitions to S3 Glacier Deep Archive cost $0.05 per 1,000 requests, so moving a million small objects costs $50 in requests before any storage saving.
  • Minimum storage durations. Objects deleted, overwritten or moved out of some classes too soon are charged a prorated fee for the remaining days: 30 days for S3 Standard-IA and S3 One Zone-IA, 90 days for S3 Glacier Instant Retrieval and S3 Glacier Flexible Retrieval, and 180 days for S3 Glacier Deep Archive. A rule that moves files to Standard-IA after 1 day and deletes them after 10 is billed as if they stayed 30.

Here are the numbers you need before you write a transition rule:

Storage classMinimum billed durationSmallest object worth movingPer-object overhead
S3 Standard-IA30 days128 KB (smaller objects are not transitioned by default)None
S3 One Zone-IA30 days128 KBNone
S3 Glacier Instant Retrieval90 days128 KBNone
S3 Glacier Flexible Retrieval90 days128 KB40 KB (8 KB at Standard rates, 32 KB at Glacier rates)
S3 Glacier Deep Archive180 days128 KB40 KB (8 KB at Standard rates, 32 KB at Deep Archive rates)

For archive classes, billing at the new rate, the 40 KB overhead and the minimum-duration clock all start when the rule is satisfied, even though the physical move happens later. If the files may need to come back, our guide to Glacier retrieval costs covers the other half of the bill.

How to prove a rule ran (or will run)

If you guess from listings, you may end up rewriting rules that were fine all along. These checks give you a definite answer.

Ask S3 when an object will expire

aws s3api head-object --bucket jakes-camera --key footage/2026/10/12/counter.mp4 \
  --query '[Expiration,LastModified]'

When an expiration rule applies to the object, the response includes an Expiration value with an expiry-date and the rule-id of the rule that will remove it. If it is missing, no current expiration rule matches that object: check the filter. If the date has passed, the object is queued and no longer billed for storage.

Watch the server access logs

If server access logging is on for the bucket, lifecycle actions appear in the log with their own operation names: S3.EXPIRE.OBJECT when an object is removed, S3.CREATE.DELETEMARKER when expiration adds a delete marker in a versioned bucket, and S3.TRANSITION_SIA.OBJECT, S3.TRANSITION_GIR.OBJECT, S3.TRANSITION_GDA.OBJECT and similar names for moves between storage classes. Searching a day's logs for S3.EXPIRE or S3.CREATE.DELETEMARKER settles "did it run?" for good.

Get notified, or watch the totals

  • S3 Event Notifications can send an event for lifecycle actions, with event types such as s3:LifecycleExpiration:Delete, s3:LifecycleExpiration:DeleteMarkerCreated and s3:LifecycleTransition.
  • S3 Storage Lens dashboards show, day by day, current-version bytes and object count, noncurrent-version bytes and count, delete marker count, and incomplete multipart upload bytes and count. If noncurrent bytes keep rising while current bytes fall, you are looking at the versioning problem above.

A bucket policy can't stop a lifecycle rule

If your rule is deleting things it should not, adding a Deny for s3:DeleteObject to the bucket policy does not help. Lifecycle actions do not use the DeleteObject operation; S3 performs them through internal endpoints, so deny statements in a bucket policy do not prevent expirations. The only ways to stop a rule are to edit it, disable it or delete it:

aws s3api delete-bucket-lifecycle --bucket jakes-camera

That removes every rule on the bucket, so save the configuration first with get-bucket-lifecycle-configuration, or disable just the one rule by setting its Status to Disabled with the get, edit, put steps above.

Recovery after a lifecycle deletion works only through versioning that was already on before the objects became eligible. With versioning, the "deleted" objects are noncurrent versions until a noncurrent-version rule removes them, and they can be restored. Without versioning, expired objects are gone. Objects in a bucket protected by AWS Backup or replicated to another bucket may also be recoverable from there.

Using lifecycle to empty a huge bucket

Lifecycle is the most practical way to empty a bucket with millions of objects. Deleting them yourself means listing every key and sending delete requests in batches of 1,000, which takes a long time for billions of objects; a rule does it in the background. Set an Expiration of 1 day on every object with an empty filter, plus the three versioning actions from the rule set above, wait for the daily runs to finish, and the bucket empties itself. For smaller buckets, the S3 console's Empty button deletes everything, including all versions, but cannot be undone; remove incomplete multipart uploads as well, since they are not objects.

Either way, check for Object Lock first. Locked versions are kept until their retention ends, and a bucket that holds them cannot be emptied, by lifecycle or by hand. Our guide to S3 delete bucket Access Denied covers locks, legal holds and the other things that keep a bucket from emptying.

A lifecycle setup that works the first time

Most of the trouble on this page comes from rules added one at a time, months apart, by different people. If you are setting up a bucket today, this order saves you every surprise above:

  1. Decide your retention per folder, in days, and write it down: footage 30 days, logs 7 days, invoices forever.
  2. Decide your undo window. If versioning is on, choose how many days deleted or overwritten data stays recoverable, and use that number for noncurrent-version expiration.
  3. Write every rule into one file, including marker cleanup and incomplete-upload cleanup, and keep that file with your other notes or in your template.
  4. Apply the file in one put, then read it back with get-bucket-lifecycle-configuration to be sure all your rules are there.
  5. Check one object the next day with head-object, and look for its expiration date and rule ID.
  6. Look at Storage Lens after a week. Current and noncurrent bytes should both level off. If noncurrent bytes keep growing, your undo-window rule is missing.

From then on, every change goes through the same file: edit it, put it, read it back. That one habit prevents the overwrite trap for good.

Troubleshooting by symptom

I added the rule today and nothing has been deleted

Rules run at 00:00 UTC, dates round to the next midnight UTC, and removal is asynchronous. Check again in two or three days, and use head-object to confirm the expiry date.

Objects past their date still appear in listings

They are queued for removal and no longer billed for storage. In large buckets, removal can lag by days.

The object count doubled after the rule ran

The bucket is versioned. Expiration added a delete marker for each object. Add NoncurrentVersionExpiration and ExpiredObjectDeleteMarker.

The bill keeps rising, but the objects look fine

Check for noncurrent versions with Storage Lens or list-object-versions, and for incomplete multipart uploads with list-multipart-uploads.

My other lifecycle rules disappeared

Someone ran put-bucket-lifecycle-configuration with only their new rule, or a template deployment replaced the configuration. Restore the rules from a saved copy or the template, and use get, edit, put from now on.

head-object shows no Expiration value

No expiration rule matches that object. Check the prefix for a leading slash, the case, the tags and the size filters, and make sure the rule's status is Enabled.

Small files never move to Glacier or Standard-IA

Objects under 128 KB are not transitioned by default. That is expected behavior, and usually a saving.

The bill went up after moving objects to Glacier

Per-object overhead, transition request charges or early-deletion fees. For many small files, archive classes can cost more than they save.

A Deny in the bucket policy did not stop deletions

Lifecycle does not use DeleteObject, so bucket policies cannot block it. Disable or delete the rule.

Nothing happens to objects under Object Lock or awaiting replication

Locked versions are never expired by lifecycle, and objects with a pending or failed replication status are skipped until replication completes.

S3 lifecycle rule not working: frequently asked questions

Why is my S3 lifecycle rule not deleting objects?

Usually timing: rules run once a day at midnight UTC, dates round up to the next midnight, and removal is asynchronous. If days have passed, check for versioning (expiration only adds delete markers), a filter that matches nothing, a disabled rule, or a rule that was overwritten.

When do S3 lifecycle rules run?

S3 evaluates lifecycle rules once a day at 00:00 UTC. It adds the rule's days to each object's creation time, rounds to the next midnight UTC, and queues eligible objects. The removal or transition then happens in the background, sometimes days later in big buckets.

How long does it take for an S3 lifecycle rule to take effect?

The first evaluation is at the next midnight UTC, and objects become eligible at midnight UTC after their age passes the rule's days. Actual removal can take extra days in large buckets. Billing for expired storage stops on the eligibility date.

Do S3 lifecycle rules apply to existing objects?

Yes. A new rule applies to objects already in the bucket as well as new ones. On its first run, every existing object that is already older than the rule's days is queued, which can take several days to process in a large bucket.

Am I charged for expired objects that haven't been deleted yet?

No. You are not charged for the storage of an object after its expiration date, even if S3 has not removed it yet. Transitions to S3 Intelligent-Tiering are the exception: billing changes only once the object has moved.

Why does my versioned S3 bucket keep growing after adding a lifecycle rule?

In a versioned bucket, expiration adds a delete marker and keeps the data as a noncurrent version, so the object count can double. Add NoncurrentVersionExpiration to permanently delete old versions, plus ExpiredObjectDeleteMarker and AbortIncompleteMultipartUpload.

How do I delete only previous versions with an S3 lifecycle rule?

Use NoncurrentVersionExpiration, called "Permanently delete noncurrent versions of objects" in the console, with the number of days after an object becomes noncurrent. It never touches current versions; a version becomes noncurrent only when it is overwritten or deleted.

Why did my other S3 lifecycle rules disappear?

PutBucketLifecycleConfiguration replaces the entire configuration. Running put-bucket-lifecycle-configuration with one rule deletes all the others. Save the configuration with get-bucket-lifecycle-configuration, add the rule to it, then put the full list back.

Can I exclude a folder from an S3 lifecycle rule?

No. Lifecycle filters cannot exclude prefixes or list several prefixes. Tag the objects you want included and filter on the tag, use a shorter shared prefix such as sales for sales1999/ and sales2000/, or write one rule per prefix.

Why don't small objects transition to Glacier?

By default, S3 does not transition objects smaller than 128 KB to any storage class. For archive classes, each object also carries a 40 KB overhead, so moving many tiny files can cost more than it saves.

How can I tell whether an S3 lifecycle rule ran?

Run aws s3api head-object on an object: an Expiration value shows the expiry date and rule ID. Server access logs record S3.EXPIRE.OBJECT, S3.CREATE.DELETEMARKER and S3.TRANSITION operations, and S3 Storage Lens shows daily object and version totals.

Can a bucket policy stop a lifecycle rule from deleting objects?

No. Lifecycle actions do not use the DeleteObject operation, so deny statements in a bucket policy do not prevent them. Edit, disable or delete the rule to stop it.

Can I recover objects deleted by an S3 lifecycle rule?

Only with versioning that was enabled before the objects became eligible, and only until a noncurrent-version rule removes the old versions. AWS Backup or replication copies may also help. Without versioning, expired objects cannot be recovered.

What is the cheapest way to delete millions of S3 objects?

A lifecycle rule: expire current versions after 1 day, permanently delete noncurrent versions, delete expired delete markers and abort incomplete multipart uploads, then let the daily runs empty the bucket in the background. DELETE requests themselves are free.

Why is an S3 lifecycle rule not deleting incomplete multipart uploads?

Expiration rules do not touch them; they need the AbortIncompleteMultipartUpload action with a number of days after initiation. List them with aws s3api list-multipart-uploads to see what is waiting.

Does an S3 lifecycle rule delete objects under Object Lock?

No. For locked objects, lifecycle adds a delete marker instead, and noncurrent versions under Object Lock are preserved, not expired. Lifecycle also skips objects whose replication is pending or failed.

Why did my S3 bill increase after transitioning to Glacier?

The 40 KB per-object overhead, transition request charges such as $0.05 per 1,000 requests to Deep Archive in US East (N. Virginia), and early-deletion fees for objects removed before 90 or 180 days can all outweigh the storage saving for small or short-lived files.

Ethan fixed both problems that evening. He put the footage rule back, together with the logs rule, added a noncurrent-version rule with a 7-day window and the cleanup for markers and abandoned uploads, and saved the whole configuration in a file next to the shop's other notes. Three nights later the camera bucket had shrunk to a month of footage, and Jake's bill was back where it started. "So the rule was right the whole time," Jake said. "It was the sheet of rules that went missing, and the old copies that never left." Ethan nodded. "Lifecycle is honest. It just runs on its own clock, and it only does exactly what the sheet says."

📌 If you keep one line from this page

Lifecycle runs at midnight UTC, versioned buckets need noncurrent-version rules, and a put replaces every rule you had.

Check with head-object, save the configuration before every change, and clean up multipart uploads too.

Revision note. Written October 12, 2026, for everyone watching a bucket that refuses to shrink. As the old saying goes, a watched pot never boils; lifecycle works overnight, so check the next morning.

Related