S3 Object Lock stores objects in a write-once-read-many (WORM) model: for a period you choose, or until you release a hold, a specific object version cannot be overwritten or deleted. It exists for two audiences. Regulated firms need records that nobody can alter, and Object Lock has been assessed by Cohasset Associates for SEC 17a-4, CFTC and FINRA requirements. Everyone else needs backups that an attacker with stolen administrator credentials cannot destroy, which is the core of ransomware defence.
Object Lock is simple to switch on and impossible to switch off, and in compliance mode a mistake can lock you into paying for data for years. This article explains how locks attach to object versions, what each mode allows, how deletes, lifecycle and replication behave, and how to design a backup bucket that survives a compromised account.
How it works: locks live on versions
Object Lock only works in buckets with S3 Versioning enabled, and every lock applies to one object version, not to a key. When you upload backup.tar three times, S3 keeps three versions, and each carries its own lock metadata: a retention mode, a retain-until date, and a legal hold status.
A lock does not stop new versions from being written or delete markers from being added. So an attacker who overwrites or deletes your objects makes them look gone, but the locked versions are still there and you can restore them by reading a specific version id or removing the delete marker. What the lock prevents is a permanent delete: a DELETE with a version id returns 403 Access Denied while the version is protected. Delete markers themselves are never WORM-protected.
Retention modes, legal holds and event holds
| Control | What it does | Who can undo it early |
|---|---|---|
| Governance mode | Blocks overwrite and delete of the version until the retain-until date | Principals with s3:BypassGovernanceRetention who send x-amz-bypass-governance-retention:true |
| Compliance mode | Same block, and the mode cannot be changed or the period shortened | Nobody, including the root user. AWS documents deleting the account as the only way |
| Legal hold | Blocks overwrite and delete with no end date, independent of retention | Anyone with s3:PutObjectLegalHold |
| Variable retention (event hold) | Protects while the hold is on; on release, retain-until becomes release time plus a duration | Depends on the mode, as above |
Any user with s3:PutObjectRetention can extend a retention period in either mode; only shortening is restricted. Legal holds and retention are independent: if retention expires while a legal hold is on, the version stays protected until the hold is removed, and bypassing governance mode does not remove a legal hold.
Use governance mode while you test and wherever a small, audited group must be able to fix mistakes. Use compliance mode only when a regulation or a deliberate security decision requires that nobody, including you, can undo it. Use legal holds for litigation or audits with no known end date. Variable retention, which AWS added recently, suits cases where the clock should start at a future business event, such as closing an account; treat its API fields as new and check the current documentation before relying on them.
Enabling Object Lock
You can enable Object Lock when you create a bucket, or later on an existing bucket. Either way the change is permanent: you cannot disable Object Lock or suspend versioning afterwards. Enabling it only makes locking possible; nothing is locked until you set a default retention on the bucket or a retention or legal hold on individual versions.
# New bucket with Object Lock (outside us-east-1, also pass
# --create-bucket-configuration LocationConstraint=<region>)
aws s3api create-bucket --bucket acme-backups-locked \
--object-lock-enabled-for-bucket
# Default retention: every new version is locked for 35 days in governance mode
aws s3api put-object-lock-configuration --bucket acme-backups-locked \
--object-lock-configuration '{"ObjectLockEnabled": "Enabled",
"Rule": {"DefaultRetention": {"Mode": "GOVERNANCE", "Days": 35}}}'The default retention applies to versions written after it is set. When an upload carries its own mode and retain-until date, those override the bucket default for that version. Existing objects in a bucket where you just enabled Object Lock are not locked retroactively; use S3 Batch Operations to apply retention to them if you need to.
Writing and inspecting locked objects
Uploads that set a retention period must carry an integrity checksum: AWS requires either the Content-MD5 header or the x-amz-sdk-checksum-algorithm header. With boto3, ask for a checksum explicitly so the request is accepted.
import boto3
from datetime import datetime, timedelta, timezone
s3 = boto3.client("s3")
bucket, key = "acme-backups-locked", "db/2026-10-01/full.dump"
until = datetime.now(timezone.utc) + timedelta(days=35)
with open("full.dump", "rb") as f:
resp = s3.put_object(
Bucket=bucket, Key=key, Body=f,
ChecksumAlgorithm="SHA256",
ObjectLockMode="GOVERNANCE",
ObjectLockRetainUntilDate=until,
)
version = resp["VersionId"]
# Inspect the lock on this exact version
print(s3.get_object_retention(Bucket=bucket, Key=key, VersionId=version)["Retention"])
# Extend retention (allowed in both modes)
s3.put_object_retention(Bucket=bucket, Key=key, VersionId=version,
Retention={"Mode": "GOVERNANCE", "RetainUntilDate": until + timedelta(days=30)})
# Place a legal hold for an audit
s3.put_object_legal_hold(Bucket=bucket, Key=key, VersionId=version,
LegalHold={"Status": "ON"})Reading lock information needs s3:GetObjectRetention and s3:GetObjectLegalHold; HeadObject and GetObject return the mode, retain-until date and legal hold status for a version. For a whole bucket, S3 Inventory can report these fields per object, but reports can be up to 48 hours old. Variable retention is set with the same retention call; the documented CLI form is:
aws s3api put-object-retention --bucket acme-backups-locked --key contracts/c-1042.pdf \
--retention '{"Mode":"COMPLIANCE","EventHold":"ON","EventHoldDuration":{"Days":30}}'
# Later, when the contract closes, release the hold: retain-until = now + 30 days
aws s3api put-object-retention --bucket acme-backups-locked --key contracts/c-1042.pdf \
--retention '{"Mode":"COMPLIANCE","EventHold":"OFF"}'
Guardrails: bucket policy and IAM
Two mistakes are worth preventing with policy. The first is a huge compliance-mode retention set by accident or by an attacker, which turns into years of storage you cannot delete. A bucket policy can cap the retention a request may set using the s3:object-lock-remaining-retention-days condition key:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "CapRetentionAt400Days",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObjectRetention",
"Resource": "arn:aws:s3:::acme-backups-locked/*",
"Condition": {"NumericGreaterThan": {"s3:object-lock-remaining-retention-days": "400"}}
}]
}The second is a governance bypass by the wrong principal. s3:BypassGovernanceRetention should be granted to almost nobody, ideally a break-glass role, and its use should alert. Also deny s3:PutBucketObjectLockConfiguration to everyone but that role, so an attacker cannot shrink the default retention for future uploads. Note that while an event hold is active, S3 recomputes the retain-until date on its side, so it can move past a limit enforced at request time; restrict the s3:object-lock-event-hold key too if that matters. The policy language itself is covered in AWS IAM conditions and AWS IAM.
Interactions with lifecycle, replication, KMS and logging
- Lifecycle. Lifecycle rules keep working, including transitions between storage classes and adding delete markers, and the lock is kept through transitions. But a lifecycle expiration cannot delete a locked version; noncurrent versions are removed only once their lock has ended.
- Replication. S3 Replication copies locked objects with their lock metadata. If the source has Object Lock, the destination must have it too, and the replication role needs
s3:GetObjectRetentionands3:GetObjectLegalHoldon the source. Event-hold releases are replicated as well. - Encryption keys. Object Lock does not protect your keys. If objects are encrypted with a KMS key and that key is deleted, the locked objects become unreadable. Protect the key with a key policy that denies scheduling deletion, as discussed in AWS KMS architecture.
- Access logs. A bucket with Object Lock cannot be the destination for server access logs.
- Delete markers. Not protected; anyone with delete permission can add or remove them.
Worked example: a backup bucket that survives a stolen admin key
A team takes a nightly 2 TB database dump and must be able to restore any of the last 30 days, even if an attacker gains administrator access to the production account.
- Separate account. The locked bucket lives in a dedicated backup account; production can only
PutObjectthrough a narrow role. Credentials stolen from production cannot change the bucket's policy. - Compliance mode, 35 days. Thirty days of required restore points plus five days of margin for late detection. Governance mode would not be enough here, because an attacker who reaches the backup account's administrator could bypass it.
- Storage arithmetic. Thirty-five nightly dumps of 2 TB is 70 TB under lock at steady state. Every version is billed until its lock ends and lifecycle removes it, so a bug that writes ten dumps a night multiplies cost tenfold for 35 days with no way to delete early. Alert on daily upload volume.
- Expiry. A lifecycle rule expires current objects after 36 days, which adds a delete marker, and expires noncurrent versions shortly after that; it cannot remove a version while its lock is active, which is the point.
- Restore drill. Monthly, delete a test object, confirm a delete marker appears, then restore from the locked version id and verify the checksum.
Failure modes
- Compliance mode with the wrong number. Ten years instead of ten days locks data and cost for ten years. Start in governance mode and cap retention with policy.
- Believing a key is protected. Only versions are protected; overwrites and delete markers still change what readers see. Restores must use version ids.
- Unlocked old data. Enabling Object Lock on an existing bucket does not lock existing objects.
- Lost key, locked data. A deleted KMS key makes locked objects permanently unreadable.
- Missing checksum. Locked uploads without
Content-MD5or an SDK checksum header are rejected. - Bypass sprawl. Broad
s3:*grants include the bypass permission and quietly turn governance mode into no protection.
Trade-offs
Object Lock trades flexibility for certainty. Governance mode gives strong protection against accidents and most insiders while leaving a controlled escape hatch; compliance mode removes the hatch entirely, which is what regulators and ransomware defence want, and what makes mistakes expensive. Versioning plus MFA Delete gives weaker protection with more flexibility, and backup services that manage their own vaults are an alternative when you do not want to manage locks per object. For the broader S3 data model, see S3 architecture.
Choosing the retention period is the decision that matters most. For regulated records, the regulation sets the minimum and legal counsel should confirm it. For ransomware defence, work backwards from detection: the period must be longer than the time between an attacker getting in and you noticing, plus the time to start a restore. Many intrusions go unnoticed for weeks, so a seven-day lock on backups protects against accidents but not against a patient attacker. Longer periods cost more because every locked version is billed until it expires, so price the steady-state volume before choosing.
Finally, remember that Object Lock protects bytes, not meaning. If an attacker corrupts data slowly and your backups faithfully copy the corrupted data for 35 days, every locked version is a perfectly preserved copy of bad data. Pair locked storage with integrity checks on the source data and keep at least some restore points older than your detection window.
What to do next
- Decide which data needs WORM storage and why: regulation, ransomware resilience, or litigation holds.
- Create a test bucket with Object Lock and governance mode, upload a version, and confirm that a versioned DELETE returns 403 and a simple DELETE adds a delete marker.
- Put the production backup bucket in a separate account and grant production only a write role.
- Add a bucket policy that caps
s3:object-lock-remaining-retention-daysand restrict bypass and lock-configuration changes to a break-glass role. - Protect the KMS key that encrypts locked data against deletion.
- Switch to compliance mode only after the retention period has been reviewed and the cost at steady state is approved.
- Run a monthly restore drill from locked version ids.