S3 versioning: the basics
What does versioning actually protect against?
S3 versioning is a per-bucket setting that keeps every version of every object. With it off — the default — an overwrite replaces the object in place and a delete removes it permanently. There is no undo, no recycle bin, no support ticket that recovers a 2GB blob you accidentally clobbered with an empty file at 3pm on a Friday.
With versioning on, an overwrite preserves the prior version under a new version-id, and a delete adds a delete marker on top of the existing object. The object isn't gone — it's hidden behind the marker, and removing the marker restores it. Every mutation becomes recoverable, by anyone with s3:GetObjectVersion and a few seconds at the CLI.
Continuity check S3-DR-001 ("Versioning Disabled") flags every bucket where the versioning status is anything other than Enabled — either Suspended (was on, was turned off) or unset (never turned on). It runs per-bucket and emits a HIGH severity finding because the blast radius of an accidental or malicious delete on a bucket holding application state, configuration, or compliance data is enormous and unrecoverable.
In this lesson you'll learn which buckets genuinely need versioning, how to turn it on without breaking anything, how to pair it with a noncurrent-version lifecycle so the storage bill doesn't compound, and when to layer Object Lock or MFA Delete on top for harder protection. You'll see the inventory query that finds unversioned buckets and the activation call that takes effect instantly.
Versioning is one-way
Once you enable versioning on a bucket, you cannot fully disable it again. AWS only lets you move it to a Suspended state — new writes stop creating versions, but every existing version stays in place until you explicitly delete it or a lifecycle rule expires it. There is no "unversion this bucket" button. This is deliberate: the original feature spec assumed that if you ever needed versioning, you needed it forever, and turning it off should never be the cause of a data-loss incident.
Enabling versioning in action
Marco runs platform engineering at a fintech. A continuity audit flags 47 unversioned buckets across the org; the highest-priority one holds the Terraform state for production infrastructure — acme-prod-tfstate. Severity: HIGH. If anyone runs terraform destroy against the wrong workspace, or fat-fingers an aws s3 rm --recursive, the entire state file is gone with no recovery path.
He's resisted enabling versioning historically because of the cost story — Terraform state files churn on every apply, and keeping every version forever could double the bucket's storage line. But the math has shifted: the bucket is small (~2GB), and a lifecycle rule expiring noncurrent versions after 60 days keeps the bill flat while still giving him a 60-day undo window for every mutation.
He pulls the bucket list to confirm which other critical buckets are also unversioned, then enables versioning on the tfstate bucket as the first move.
First, list every bucket in the account whose versioning status is anything other than Enabled — these are the candidates the continuity check is flagging.
Bucket inventory with versioning status. None means never turned on; Suspended means it was on and turned off.
Now enable versioning on the tfstate bucket. The call is instant — new writes will start creating versions immediately.
Enabling versioning is a single API call, takes effect immediately, and never blocks reads or writes.
Versioning under the hooddeep dive
Every object in a versioned bucket gets a unique VersionId — a URL-safe string assigned at write time. The bucket index keeps every version of every key, ordered by write time, with one designated as the "current" version. A GET without a version-id returns the current version; a GET with ?versionId=... returns that exact version. A DELETE without a version-id writes a zero-byte delete marker that becomes the new current version, hiding everything below it. A DELETE with a specific version-id permanently removes that version.
Object Lock is a separate layer that sits on top of versioning. It requires versioning to be enabled (you can't have Lock without versions to lock). Two modes: Governance lets IAM principals with the s3:BypassGovernanceRetention permission override the lock; Compliance doesn't — once an object is locked in compliance mode for a retention period, nobody, not even the root account, can delete it before the period expires. This is the layer that protects against ransomware: an attacker with full IAM permissions still can't delete locked versions.
Storage cost grows linearly with the number of versions retained. Every version is billed as a separate object at its storage class. This is why an enabled-and-untouched versioning policy can quietly double or triple a bucket's storage bill over a year — every overwrite keeps the old version forever. The fix is a Lifecycle rule with NoncurrentVersionExpiration set to 30-90 days depending on how long you actually need to be able to roll back.
# Inspect every version of a key — what versioning actually gives you.
aws s3api list-object-versions \
--bucket acme-prod-tfstate \
--prefix env/prod/terraform.tfstate
# Recover a deleted object by removing the delete marker.
aws s3api delete-object \
--bucket acme-prod-tfstate \
--key env/prod/terraform.tfstate \
--version-id <delete-marker-version-id> What is the impact of running unversioned?
The most direct impact is unrecoverable data loss. An accidental aws s3 rm, a bad CI/CD pipeline that uploads the wrong artefact, a misconfigured terraform apply that destroys state, a compromised CI key that wipes a bucket — every one of these is permanent on an unversioned bucket. The cost is whatever it takes to rebuild the data: hours, days, or in the case of customer-generated content, never.
The second-order impact is operational: without versioning, every change to a critical object becomes a high-stakes event. Engineers slow down, add review gates, manually back up files before edits — all because the underlying storage doesn't have an undo. The friction is invisible on the bill but very real in delivery speed.
The third-order impact is regulatory. SOC 2, ISO 27001, HIPAA, and most financial regulators expect demonstrable backup and recovery capability for systems handling regulated data. "We rely on S3 durability" is not an acceptable answer when an auditor asks how you recover from an accidental delete — S3's 11-nines durability protects against disk failure, not human or programmatic error.
Versioning does add real cost — typically 10-40% extra storage on busy buckets if left unbounded. That's why the right shape is always versioning enabled plus a noncurrent-version lifecycle rule. Storage you keep for 30-90 days as a safety net, then expire. The cost of the safety net is almost always trivial compared to the cost of not having it the one time it's needed.
How do you enable versioning safely?
Enabling versioning is a four-step loop. The activation itself is instant and harmless; the surrounding steps make sure you don't end up paying for protection you can't afford, or skipping protection you needed.
1. Inventory and triage which buckets need it
Not every bucket needs versioning. Buckets that should always have it: anything holding application state, configuration, secrets, Terraform/Pulumi state, customer-uploaded content, audit logs, or anything compliance-relevant. Buckets that may not: pure data-pipeline staging that's deterministically re-derived from an upstream source on every run, or temporary scratch buckets with explicit short TTLs. Walk the list and tag each bucket with critical, replaceable, or scratch before flipping anything on.
2. Enable versioning + a noncurrent-version lifecycle in the same change
Turn versioning on with put-bucket-versioning Status=Enabled and immediately add a Lifecycle rule expiring noncurrent versions after 30-90 days (90 is a common audit-friendly default; 30 is fine for high-churn buckets where storage cost is the constraint). Without the lifecycle, storage cost grows forever; with it, the bucket has a bounded recovery window and a bounded bill.
3. For ransomware-grade protection, layer Object Lock
For buckets holding compliance evidence, audit logs, or backups that must survive a credential compromise, enable Object Lock in Compliance mode with a retention period appropriate to the data (often 7 years for financial records). Object Lock requires versioning, must be enabled at bucket creation or via support ticket, and once a version is locked it cannot be deleted until the retention period expires — by anyone, including root. Use Governance mode if you need an emergency-break escape valve for trusted admins.
4. Prevent recurrence with AWS Config and SCPs
Enable the AWS Config managed rule s3-bucket-versioning-enabled to fire whenever someone creates a new bucket without versioning, or suspends versioning on an existing one. At the organisation level, add an SCP that denies s3:PutBucketVersioning calls with Status=Suspended for production accounts — engineers can enable but cannot disable. The combination catches both accidental misconfiguration and deliberate exfiltration prep.
# Enable versioning + add a 90-day noncurrent-version expiration in the same change.
aws s3api put-bucket-versioning \
--bucket acme-prod-tfstate \
--versioning-configuration Status=Enabled
aws s3api put-bucket-lifecycle-configuration \
--bucket acme-prod-tfstate \
--lifecycle-configuration '{
"Rules": [{
"ID": "expire-noncurrent-90d",
"Status": "Enabled",
"Filter": {},
"NoncurrentVersionExpiration": { "NoncurrentDays": 90 }
}]
}' Quick quiz
Question 1 of 5You've just enabled versioning on a production bucket holding Terraform state. What's the most important follow-up step to make sure the change is operationally sound?
You scored
0 / 5
Keep learning
Dig deeper into S3 data-protection and durability primitives.
- Using versioning in S3 buckets Official AWS docs on enabling, suspending, and querying versioning state.
- S3 Object Lock Governance vs Compliance modes, retention periods, legal holds, and ransomware protection.
- Lifecycle configuration for versioned buckets Example lifecycle rules that expire noncurrent versions on a schedule — the cost-control half of the equation.
- AWS Config managed rule: s3-bucket-versioning-enabled Continuous detection for unversioned buckets across the org.
You've completed Enable S3 versioning on critical buckets. You now know which buckets need versioning, how to enable it without unbounded cost, when to layer Object Lock for ransomware-grade protection, and how to prevent the setting from being silently disabled. The next time a continuity audit flags an unversioned bucket, you'll have a four-step loop ready to run.
Back to the library