object lockworm storages3retention policycompliance

Object Lock and WORM Storage: How Immutability Works

September 2026 · 24 min read · Surya

Object Lock and WORM Storage: How Immutability Works

AccessDenied: Object Lock configuration cannot be enabled on existing buckets

That is the error most people see the first time they meet it. You create a bucket, you poke around the console, you find a checkbox that says "Object Lock," you click it, and the API tells you to get lost.

This post is about what object lock actually is, why that checkbox exists, and how to turn it on without locking yourself into something you regret. It is a walkthrough. There is a checklist at the end. There is also a section where we tell you not to use us, so if that is what you came for, scroll down.

Here is what we cover:

  • What WORM actually means under the hood
  • Why you cannot turn it on after bucket creation, and what to do if your bucket already has data
  • Governance mode versus Compliance mode, and the failure mode of each
  • How to pick a retention period you can defend
  • How to apply it to objects that are already in the bucket
  • What a legal hold is and when to use one
  • Why your provider's implementation matters more than the S3 spec
  • Who should not use Storafleet for this

A retention date set in the console is a promise your provider has to keep

Object lock is not a product. It is a set of rules your storage provider enforces on object versions. That is the whole idea. You attach two things to an object version: a retention timestamp (a date the object cannot be deleted or overwritten before) and optionally a legal hold flag (an on/off switch that blocks deletion regardless of the timestamp). The provider's API enforces both. Your client doesn't.

This matters because people talk about "WORM storage" like it is a box you buy. Write Once Read Many. A tape library with a write-protect tab. A NAS with an immutable snapshot feature. Those are all real things, and object lock is the cloud-object-storage equivalent. But the mechanism is different. There is no physical write-protect. There is no hardware interlock. There is a policy on the provider side that says: this version exists, its retention date is 2029-03-15T00:00:00Z, and any DELETE or PUT that would remove or overwrite it will be rejected by the API until that date passes.

WORM storage, in the S3-compatible world, is a property of the bucket. You don't buy a WORM appliance. You create a bucket with the setting enabled, and then every object you put into it can carry a retention date. The provider holds the line.

There is a versioning detail that trips people up. Object lock operates on object versions, not objects. If you put report.pdf into a locked bucket with a retention date, and then you put a new report.pdf, you now have two versions. The first is locked. The second is a new version with its own retention rules (or none, unless a default applies). A delete without a version ID just adds a delete marker; the locked version is still there. A delete with the locked version's ID gets rejected. This is why object lock requires versioning. We will get to that.

The other thing to internalise: the retention date is a floor, not a ceiling. You can always extend it. In Governance mode you can shorten it if you have the right permission. In Compliance mode you cannot shorten it at all. The date is a promise. The provider keeps it.

Object Lock is a bucket setting you cannot turn on after the fact

On AWS S3, and on most S3-compatible providers that copy the S3 API, object lock must be enabled at bucket creation time. Not after. Not "flip a switch later." At creation.

That is the error at the top of this post. You have a bucket. It has data. You want object lock. The API says no.

The reason is versioning. Object lock requires versioning to be enabled on the bucket, and on S3, versioning has to be on when object lock is turned on. You cannot enable object lock on a bucket that already has unversioned objects, because those objects have no version IDs, and object lock works on version IDs. The provider will not retroactively version them for you. It refuses.

So the prerequisite list is short and unforgiving:

  1. Versioning must be enabled on the bucket.
  2. Object lock must be enabled at bucket creation.
  3. Both must happen together. You cannot enable versioning, wait a week, then enable object lock.

If you are creating a new bucket, this is easy. In the AWS console, when you create a bucket, there is an "Advanced settings" section with a checkbox for object lock. In the CLI:

aws s3api create-bucket \
  --bucket my-locked-bucket \
  --object-lock-enabled-for-bucket \
  --region us-east-1

That flag is the whole game. Without it, the bucket is a normal bucket and you cannot add object lock later.

What if the bucket already exists and has data? You have two options, and neither is "turn it on in place."

Option one: create a new bucket with object lock enabled, copy the data over, and switch your application to the new bucket. The copy is a normal PUT, and if you want the destination objects locked, you either apply a default retention rule to the bucket (more on that in a moment) or set per-object retention as you copy. This is the clean path. It is also the path that costs you egress on AWS, which is not free.

Option two: if the data is disposable or you have a maintenance window, export it, delete the bucket, recreate it with object lock, and re-import. Same result, more downtime.

There is no option three. There is no API call that adds object lock to an existing bucket with unversioned data. If a provider claims to offer that, read their docs very carefully, because they are either doing something non-standard or they are describing a different feature.

One more thing: on AWS, if you enable object lock at creation, versioning is automatically enabled. You do not have to turn it on separately. On some S3-compatible providers, you do. Check.

Governance mode is a lock with a key taped to the front (and Compliance mode is not)

Step one of the actual procedure is choosing the mode, and this is the decision that will hurt you if you get it wrong.

Governance vs Compliance mode

Object lock has two modes. They are not interchangeable, and you choose per-object (or per default rule) when you set retention.

Governance mode is a lock you can bypass. If you have the s3:BypassGovernanceRetention permission on your IAM principal, and you send the request with the x-amz-bypass-governance-retention:true header, you can delete or overwrite the object before its retention date. This is the mode most people should start with. It protects you from accidents, from a rogue script, from a misconfigured lifecycle rule. It does not protect you from a determined admin with the right permission. That is usually fine.

Compliance mode is a lock with no key. Once an object version is in Compliance mode with a retention date, nobody can delete it or shorten the retention before that date. Not you. Not your root account. Not AWS support. Not a court order, as far as the API is concerned. The object sits there until the clock runs out. You can extend the retention. You cannot reduce it.

Here is the concrete failure. You write a script. The script has a bug. The bug sets the retention date to 2124-01-01 instead of 2024-01-01. You are now paying for that object for a hundred years. There is no undo. You cannot delete the bucket. You cannot delete the object. You can only wait, or pay.

We have seen this movie. Not with a hundred-year date, but with a fat-fingered year, and with a misapplied default rule that locked a pile of test data for a decade. The bill is not catastrophic for a few gigabytes. It is catastrophic for a few terabytes, over years, because the storage cost compounds and you cannot get rid of it.

The rule we would give you: start in Governance mode. Move to Compliance mode only when a regulation or a contract actually requires it, and only after you have tested your retention-setting code against a throwaway bucket.

If you are in a regulated industry (finance, healthcare, certain government contracts), Compliance mode is often the point. The whole reason you are here is that an auditor wants to know the data cannot be deleted, even by you. Governance mode does not satisfy that, because you can bypass it. So you use Compliance mode, and you accept the risk, and you test the hell out of your date logic before you point it at production.

One detail: you can have both modes in the same bucket, on different objects. The mode is set per retention rule, not per bucket. A default rule can be Governance mode, and specific objects can be Compliance mode, or vice versa. This is useful and also a way to confuse yourself. Document which is which.

Pick a retention period you can defend, because the calendar does not negotiate

Step two: set the retention period. Tie it to an actual obligation, not a round number.

The temptation is to pick something like "seven years" because that sounds like a records-retention number. Sometimes it is. Sometimes it is not. The right period is the one you can point at and say: this is why.

Common obligations that map to a retention period:

  • Regulatory. Financial records, medical records, certain government data. The period is specified by the regulation. Find the number. Do not guess.
  • Contractual. A customer contract says you retain their data for X years after termination. That is your period.
  • CCTV footage. This is one of the biggest real-world uses of object lock, and the period is usually set by policy or by law. Thirty days is common. Ninety days is common. Some jurisdictions require longer for certain incident types. The point is that the footage cannot be deleted early, which matters when someone asks for it after an incident and the retention window has already closed. If you are searching for "cctv worm," this is the feature you want: the camera writes to a bucket with a default retention rule, and nobody can delete the footage before the period expires, not even the admin who is trying to cover something up.
  • Internal policy. You decided to keep audit logs for two years. Fine. Write it down.

Default retention rules versus per-object retention. This is the distinction that matters for the next step.

A default retention rule is set on the bucket. It says: any object put into this bucket without an explicit retention gets this retention period and mode. It is a catch-all. It is also a blunt instrument. If you set a ten-year default rule and then dump a bunch of test data into the bucket, that test data is now locked for ten years. We will come back to this.

Per-object retention is set when you put the object, or applied later to a specific version. It overrides the default. You can set a different period, or a different mode, or both. This is how you handle the case where most objects need seven years but a few need ten.

In Compliance mode, per-object retention can only be extended, never shortened. If the default rule says seven years and you set a specific object to ten, that is fine. If the default says ten and you try to set a specific object to seven, the API rejects it. The longer period wins. The calendar does not negotiate.

In Governance mode, you can shorten with the bypass permission. That is the escape hatch. Use it sparingly.

One more thing about the period: it starts when the object version is created, not when you apply the rule. If you put an object six months ago and apply a seven-year retention today, the retention date is seven years from today, not seven years from the object's creation. If your obligation is "retain for seven years from creation," you need to set the retention at creation time, or calculate the right end date and apply that. This is a small detail that bites people during audits.

Step 3: apply object lock to what is already in the bucket, carefully

This is the step where people get stuck, and it is the step most guides skip.

Prerequisites and paths for object lock

You have a bucket with object lock enabled. You have a default retention rule set. You upload a new object. It is locked. Good.

But what about the objects that were already in the bucket before you set the default rule? They are not locked. Default rules apply to new objects, not existing ones. If you created the bucket with object lock enabled and then uploaded a million objects and then set a default rule, the first million objects have no retention. They are sitting in a bucket that looks locked but is not, for those objects.

This is a real gap. If your compliance story is "everything in this bucket is immutable," you need to close it.

The order that works:

  1. Enumerate. List every object version in the bucket. Not every object. Every version. Object lock is per-version, and if you have versioning on (you do, you have to), you have versions. aws s3api list-object-versions --bucket my-bucket is your friend. For large buckets, paginate, and be aware that this can take a while.
  2. Exclude the junk prefixes. Every bucket has them. tmp/, test/, dev/, _attic/, the output of a misconfigured logging job. You do not want to lock these for seven years. Filter them out before you apply retention. If you have already set a default rule that catches them, you have a problem, and the next section is about that.
  3. Apply per-object retention to the ones that need it. For each version that should be locked, call put-object-retention with the mode and the date. This is an API call per version. There is no bulk operation. If you have a million versions, you have a million calls. Script it, rate-limit it, and do not run it from a laptop on a coffee shop wifi.
  4. Then turn on the default rule. Once the existing objects are handled, set the default retention rule so new objects are covered automatically. If you set the default rule first, you lock the junk, and you cannot undo it in Compliance mode.

The order matters. Default rule first is the mistake. It is the mistake because it is the easy path: you see the "Default retention" setting in the console, you fill it in, you feel done. But you have just locked everything that arrives from now on, including the test object you upload five minutes later to check that it works, and including the output of whatever background job writes to that bucket. If you are in Compliance mode, those are locked for the full period. No undo.

We have watched people do this with a ninety-day default rule and a nightly job that writes a few hundred gigabytes of intermediate data to the same bucket. Ninety days later, the bill is real, and the intermediate data is still there, and there is nothing to do but wait.

The other thing to check: if you are applying retention to existing objects, and those objects are in a bucket with a default rule already set, the per-object retention you apply has to be at least as long as the default, if the default is in Compliance mode. Otherwise the API rejects it. Read the error, do not just retry.

Once you put a legal hold on, only a human can take it off

Legal hold is a separate flag from retention. It has no end date. It stays until someone with the right permission removes it.

The use case is litigation or investigation. You have a legal obligation to preserve certain data, and you do not know when the obligation ends, because you do not know when the case ends. A retention date is the wrong tool, because you would have to guess. A legal hold is the right tool, because it just says: this object cannot be deleted until a human says otherwise.

Legal hold and retention can both be active on the same object version. The object is protected by both. To delete it, you need the retention date to have passed and the legal hold to be removed. Either one alone blocks deletion.

The permission is s3:PutObjectLegalHold. You need it to set or clear the hold. You also need to think about who has it. If everyone in your org has it, a legal hold is not much of a hold. If only one person has it, you have a bus factor problem. This is an IAM design question, and the answer depends on your org, but the question is worth asking before you need it.

Setting a legal hold:

aws s3api put-object-legal-hold \
  --bucket my-bucket \
  --key evidence/incident-2024-03.tar.gz \
  --version-id 3sL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo \
  --legal-hold Status=ON

Clearing it is the same call with Status=OFF.

Two things people get wrong here. First, they set a legal hold and forget about it. The hold outlives the case, and now you have data you cannot delete, and nobody remembers why. Keep a list. The list is not optional. Second, they assume a legal hold is a substitute for Compliance mode. It is not. A legal hold can be removed by anyone with the permission. Compliance mode cannot be removed by anyone. If the requirement is "this cannot be deleted, period," you want Compliance mode. If the requirement is "we need to preserve this until the lawyers say stop," you want a legal hold.

And yes, you can have a Governance-mode retention date plus a legal hold. That is a common combination for investigation data: the retention date is a backstop, the legal hold is the active control, and when the hold comes off, the retention date still applies until it expires.

Your provider's implementation is the one that matters

AWS S3 is the reference implementation. Everyone else is somewhere on a spectrum from "full parity" to "we have a checkbox that does something."

This is the part where you have to read your provider's docs, not the S3 docs. The S3 API is a specification, and S3-compatible providers implement subsets of it, sometimes with quirks. Object lock is one of the features where the gaps are real and the gaps matter.

Some things to check for any provider:

  • Do they support object lock at all? Not every S3-compatible provider does.
  • Do they support both Governance and Compliance modes, or only one? Some providers only support Governance mode. If your requirement is Compliance mode, that provider is out.
  • Is object lock bucket-creation-only, or can it be enabled later? Most copy the S3 rule (creation only). Some do not. Check.
  • Do they require a support ticket to enable it? Some do. That is a real operational difference.
  • What is the minimum and maximum retention period? Some providers have limits.
  • How does it interact with lifecycle rules? Can a lifecycle rule delete an object that is under retention? It should not. Verify.

Provider-specific notes, based on what we have seen and what the providers document. Check current docs before you rely on any of this, because it changes:

AWS S3 is the reference. Full object lock support, both modes, legal hold, default and per-object retention. The AWS object lock documentation is the doc everyone else is compared against. If you want "aws s3 object lock worm immutability documentation," that is where to start.

Wasabi supports object lock, and their docs cover it. Check the mode support and the bucket-creation rule in their current documentation, because it has evolved.

MinIO supports object lock, and since it is self-hosted, you are the provider. The enforcement is in the MinIO server you run. That is both the strength (you control it) and the weakness (you are the one who has to get it right, and if you have admin access to the underlying disks, "immutable" means something different than it does in a managed cloud). If you are searching for "minio object lock worm," you are in the right place, and you should read MinIO's own docs on the topic. The self-hosted case is genuinely different from the managed case, and the threat model is different too.

Cloudflare R2 supports object lock, and if you are searching for "cloudflare r2 object lock," check their docs for the current state of mode support and any bucket-creation requirements. R2 has been adding features steadily, and the docs are the source of truth.

Backblaze B2 has object lock support via its S3-compatible API, and also has its own native file-lock feature. The two are not the same thing, and the native feature may have different semantics. If you are using B2, read both sets of docs and decide which one you want.

The general rule: do not assume S3 parity. Test it. Create a bucket, enable object lock, put an object, set a retention date, try to delete it, and see what happens. Do this before you trust the feature with anything that matters. The test takes ten minutes and it will tell you more than any comparison table.

This is also where Storafleet sits. We are a control plane for object storage you already own. We connect to 50+ S3-compatible providers plus custom endpoints, and we expose object-lock configuration per bucket for the providers that support it. You set the mode, the retention period, and the default rule through our console, and the calls go to your provider's API. The enforcement is theirs, not ours. We never store your file contents, so we cannot enforce anything even if we wanted to. The object lock guide in our docs walks through the per-provider support we see and the configuration flow.

What we do not do is fix a provider's gaps. If your provider only supports Governance mode, we can set Governance mode. We cannot make Compliance mode appear. If your provider requires a support ticket to enable object lock, you still have to open the ticket. We are a console over the provider's API, and the provider's API is the ceiling.

Who should not use Storafleet for object lock, and what to use instead

Here is the honest version. There are several people reading this who should not use us, and we would rather say so than have you find out after you have paid.

If your budget for this is zero, the conversation is over and rclone wins. rclone is free, permanently, with no account and no vendor. It supports more backends than we do (70+, including the consumer clouds we do not touch). It is scriptable. It runs inside your own security boundary. It has been maintained for over a decade. For object lock specifically, rclone can set retention and legal hold on S3-compatible backends, and it can do it from a shell script or a cron job. The rclone backend command and the --s3-object-lock-* flags cover the same ground our console covers, and they cover it without a subscription. If you are comfortable in a terminal and you do not want to pay for a control plane, use rclone. We mean that. There is no version of this where we are a better answer for a zero-budget workflow.

If your credentials must never leave hardware you control, use rclone, or use our IAM-role connect if it is AWS only. Our credentials sit encrypted in our database. The encryption is real: AES-256-GCM with per-namespace HKDF keys. AWS can be connected keylessly via IAM role assumption, so no long-lived key ever touches us for that provider. But "the keys never left my laptop" is a stronger answer than ours, and it is dishonest to pretend otherwise. If that is your requirement, rclone runs on your machine and the keys stay there. For non-AWS providers, that is the only answer we can give you.

If everything lives in one cloud and always will, that provider's own console is free and more current than we are. If you are all-in on AWS, the S3 console has object lock support, it is authoritative, and it will have the newest features the day they ship. We will have them when we get to them. For a single-cloud shop, the provider console is the better tool. Full stop.

If your workflow is entirely code and always will be, use a CLI. The AWS CLI, or rclone, or a provider-specific CLI. We are a console, not a command. We do not compose with cron, systemd, CI pipelines, shell scripts and Makefiles the way a binary does. If your object-lock configuration is a step in a Terraform apply or a GitHub Action, a CLI fits that natively and a web UI never will. We are not going to pretend a console is the right interface for a pipeline.

If you need your buckets mounted as a local drive, we cannot do it. There is no FUSE surface in Storafleet. If you want remote buckets to appear in Finder or Explorer, rclone mount, Mountain Duck or ExpanDrive. That is not a feature we have on a roadmap. It is a different product category.

If you need Google Drive, Dropbox, OneDrive, Box, SFTP or WebDAV, we are the wrong tool. We connect the S3-compatible family and custom S3 endpoints. Nothing else. If the job is Drive-to-bucket, use rclone or MultCloud. MultCloud and CloudFuze cover the consumer clouds we deliberately do not.

And one more, which is about us rather than the alternatives. We have no published case studies and no named customers to point at. We will not invent them. We are a small team, and Storafleet started in 2026. If the question "will this still exist in five years" is load-bearing for you, a decade-old open-source project has a better answer than we do. rclone will outlive us. That is just true.

So who is Storafleet for? People managing object storage across more than one provider, who want a single console for configuration including object lock, who are not going to write the code themselves, and who want a flat subscription rather than per-gigabyte egress fees. If that is you, we are a good fit. If it is not, the alternatives above are real and we are not going to talk you out of them.

The checklist

Before you lock anything real, run this list. Print it. Tape it to the wall.

  1. Versioning is on. Object lock requires it, and on most providers it has to be on at bucket creation alongside object lock. Confirm both.
  2. You chose a mode. Governance mode unless a regulation or contract requires Compliance mode. If you chose Compliance mode, you tested your date logic against a throwaway bucket first.
  3. You set a retention period you can defend. Tied to a regulation, a contract, a CCTV policy or a written internal policy. Not a round number you liked.
  4. You scanned the existing objects. You know how many versions are in the bucket, you know which prefixes are junk, and you have a plan for the ones that need retention.
  5. You applied retention to existing objects before setting the default rule. Not after. The default rule locks new objects, and in Compliance mode there is no undo.
  6. You understand legal hold. Who can set it, who can clear it, and where the list is kept. A hold with no owner is data you can never delete.
  7. You read your provider's docs, not just the S3 docs. Mode support, bucket-creation rule, support-ticket requirements, retention limits. All of it.
  8. You locked a test object and verified it. Put an object, set a retention date, try to delete it, confirm the API rejects it. Do this before you point the feature at production data.

That last one is the one people skip. It is also the one that catches the provider whose object lock implementation does not do what the docs say. Test it. Then lock the real data.

The error at the top of this post is annoying, but it is also a gift. It tells you that object lock is a bucket-creation decision, and it forces you to think before you act. The people who get into trouble are the ones who never see that error, because they created the bucket with object lock enabled by accident, set a default rule, and locked a pile of data they cannot get rid of. If you see the error, you are already being careful. Keep going.

Your storage estate deserves a control plane.

Join the DevOps teams and founders who run every cloud's buckets from one control plane.

Free plan  ·  No credit card  ·  50+ cloud providers  ·  Cancel any time