skip to content

Versioning, Consistency & Object Lock

S3 has been strongly read-after-write consistent since 2020, and versioning keeps every overwrite and delete recoverable behind delete markers. Interviewers reach for this when asking how I protect a bucket against an accidental — or malicious — delete.

part ofAWSoverview, primer and where to startread it →
on this pageshow

questions

5

In an Amazon S3 bucket with versioning enabled, what happens when a client sends a DELETE for an object key without specifying a version ID, and how do you get the object back?

level: juniorimportance: must knowfreq 68%

answer

  1. nothing is erased on delete
  2. a new current version with no body
  3. 404 to the client, bytes still billed
  4. remove the marker by its version ID
  5. s3:DeleteObject vs s3:DeleteObjectVersion

basics

~20 s

S3 erases nothing: it adds a delete marker as the new current version, so plain GETs return 404 while every earlier version stays stored and billed. You restore by deleting the delete marker by its own version ID.

solid answer

~40 s

With versioning enabled, a `DELETE` that omits a version ID is not a destructive operation. S3 inserts a **delete marker** — a zero-byte placeholder version with its own fresh version ID — and makes it the current version of the key. A normal `GetObject` then returns 404 with an `x-amz-delete-marker: true` response header, but the real bytes are untouched as noncurrent versions. To undelete, call `ListObjectVersions` for the key, find the delete marker, and issue `DeleteObject` with `--version-id` pointing at the marker; removing the marker promotes the most recent real version back to current. Permanently destroying data requires a `DELETE` that names a real version ID, which is a different IAM action (`s3:DeleteObjectVersion`) from `s3:DeleteObject`. That split is the whole point: application credentials can "delete" objects without being able to lose them.

code

bash · 16 lines
bash
BUCKET=my-bucket
KEY=report.pdf

# Versioned DELETE with no version ID: S3 inserts a delete marker, erases nothing
MARKER=$(aws s3api delete-object --bucket "$BUCKET" --key "$KEY" \
  --query VersionId --output text)

# The key now reads as missing
aws s3api head-object --bucket "$BUCKET" --key "$KEY" || echo "404 - shadowed by marker"

# Every real version is still listed
aws s3api list-object-versions --bucket "$BUCKET" --prefix "$KEY"

# Undelete: remove the delete marker by its own version ID
aws s3api delete-object --bucket "$BUCKET" --key "$KEY" --version-id "$MARKER"
aws s3api get-object --bucket "$BUCKET" --key "$KEY" restored.pdf

go deeper

for a junior

Be able to say plainly that a delete in a versioned bucket adds a delete marker, GETs then return 404, and the object comes back by deleting that marker by its version ID.

for a middle

Explain the mechanics: the marker is a bodyless version with its own ID, 404 versus 405 on reads, ListObjectVersions versus ListObjectsV2, and that every retained version is billed in full.

for a senior

Show the operational judgment — split s3:DeleteObject from s3:DeleteObjectVersion across roles, pair versioning with a retention policy so the bucket does not grow forever, and know that versioning alone does not stop a principal that holds version-level delete.

for a principal

Own the policy: decide which buckets get versioning by default across the estate, what the recovery objective is, who holds break-glass version deletion, and how the retained-version cost is budgeted and charged back.

## The two shapes of DELETE S3 has one `DeleteObject` API but two very different behaviours, decided by whether the request carries a version ID and whether the bucket is versioned. On a bucket with versioning **enabled**, `DELETE /key` with no version ID is a *logical* delete. S3 creates a new version of the key called a **delete marker** and makes it the current version. Nothing is overwritten and nothing is freed. `DELETE /key?versionId=<id>` is the *physical* delete: it removes exactly that one version's bytes, permanently, with no undo. On an unversioned bucket there is only one shape — the object is gone. ## What a delete marker actually is A delete marker is a version of the key like any other. It has a unique version ID, a last-modified timestamp, and an owner; what it does not have is a body. It stores only metadata, so its storage cost is negligible, but it is visible in `ListObjectVersions` and it shadows everything behind it. The response to the deleting call tells you what happened: ```bash aws s3api delete-object --bucket my-bucket --key report.pdf # { "DeleteMarker": true, "VersionId": "L4kqtJlcpXro..." } ``` The `DeleteMarker: true` field and the `x-amz-delete-marker` response header are how a client distinguishes "a marker was inserted" from "a version was destroyed". ## Reading a key that is shadowed by a marker - `GetObject` / `HeadObject` with no version ID → **404 Not Found**, with `x-amz-delete-marker: true` and `x-amz-version-id` naming the marker. - `GetObject` with the *delete marker's* version ID → **405 Method Not Allowed**. You cannot download a marker; it has no body. - `GetObject` with a real noncurrent version's ID → **200**, the original bytes. The data was never gone. - `ListObjectsV2` (the flat listing most tooling uses) does not show the key at all; only `ListObjectVersions` reveals the marker and the versions behind it. That asymmetry explains a common support ticket: the console and the SDK both say the object does not exist, while the storage bill has not moved a cent. ## Undeleting Restoring is simply removing the shadow: ```bash # find the marker aws s3api list-object-versions --bucket my-bucket --prefix report.pdf # delete the marker itself, by its version ID aws s3api delete-object --bucket my-bucket --key report.pdf \ --version-id L4kqtJlcpXro... ``` Once the marker is gone, the next most recent version becomes current again and plain GETs work. The alternative, useful when you want an audit trail rather than a rollback, is to `CopyObject` the old version onto the same key — that creates a brand-new current version whose content is the old one, leaving the marker in history. ## The permission split that makes this useful The protective value of versioning is entirely in IAM. `s3:DeleteObject` allows the logical delete (marker insertion). `s3:DeleteObjectVersion` allows the physical one. Grant an application only the former, keep the latter for a small break-glass role, and a compromised or buggy application literally cannot destroy stored data — it can only hide it. Reading old versions likewise needs `s3:GetObjectVersion`, not just `s3:GetObject`. This is also why "we have versioning on" is not by itself an answer to a ransomware question: if the same principal holds `s3:DeleteObjectVersion`, an attacker can walk the version list and remove every version. Versioning is the mechanism; the permission split, and stronger primitives layered on top of it, are the control. ## Costs and housekeeping Every retained version is billed at the full size of that version in its storage class — S3 stores whole objects, not deltas, so ten uploads of a 1 GB file cost 10 GB. Delete markers themselves are effectively free, but they accumulate. A versioned bucket therefore needs an explicit retention policy (noncurrent-version expiration) or it grows forever; without one, "we deleted that data last year" is usually false. ## Enabling and the states Versioning is a bucket-level setting, off by default, turned on with `PutBucketVersioning` (`Status=Enabled`). It applies only to objects written after it is enabled: objects that already existed get the literal version ID `null` until they are next overwritten. And once enabled, a bucket can only be *suspended*, never returned to a truly unversioned state.

  • What status code does a GET return for a key shadowed by a delete marker, and what if you request the marker's version ID directly?
    A plain `GetObject` returns 404 Not Found, with `x-amz-delete-marker: true` and `x-amz-version-id` identifying the marker. Requesting the marker's own version ID returns 405 Method Not Allowed, because a delete marker has no body to return. Requesting a real noncurrent version's ID returns 200 and the original bytes.
  • Which IAM permissions would you grant an application so it can "delete" objects but cannot actually lose data?
    Grant `s3:PutObject`, `s3:GetObject` and `s3:DeleteObject`, and withhold `s3:DeleteObjectVersion` (and usually `s3:PutBucketVersioning`). The application can then only insert delete markers. Keep version-level deletion in a separate break-glass role so a compromised application credential cannot walk the version list and destroy history.
  • Does enabling versioning protect objects that were already in the bucket?
    Yes, but they carry the literal version ID `null` until they are next overwritten. A pre-existing object has exactly one version, the null one; the first PUT after enabling versioning gives it a real version ID and preserves the null version as noncurrent. So protection starts at enablement, not retroactively.

saying these in an interview costs you the question

  • Says a versioned DELETE frees storage immediately
  • Thinks S3 stores diffs between versions rather than full copies
  • Assumes s3:DeleteObject alone can permanently destroy data
  • Claims restoring requires re-uploading from an external backup
  • Believes ListObjectsV2 shows delete markers

context

open as a page

Amazon S3 has provided strong read-after-write consistency since December 2020. What exactly does that guarantee cover, and which S3 behaviours are still not covered by it?

level: middleimportance: must knowfreq 55%

basics

~20 s

Since December 2020, any successful S3 PUT or DELETE is immediately visible to every subsequent GET, HEAD and LIST, in all regions, at no extra cost. Bucket configuration changes and replication to another bucket remain eventually consistent.

open as a page

A versioning-enabled S3 bucket's storage bill keeps climbing even though the team has a lifecycle rule that expires objects after 30 days. Why is the data not going away, and what would you change?

level: seniorimportance: should knowfreq 42%

basics

~20 s

On a versioned bucket, an Expiration rule only makes the current version noncurrent by adding a delete marker; the bytes stay as noncurrent versions and keep billing. Add NoncurrentVersionExpiration, plus a rule with ExpiredObjectDeleteMarker to sweep the markers.

open as a page

A compliance team requires that audit logs in S3 cannot be deleted by anyone — including an administrator whose credentials are stolen — for seven years. How would you build that on S3, and what are the tradeoffs of the mode you choose?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Enable S3 Object Lock on the versioned bucket with a seven-year default retention in compliance mode, so no principal — including the account root user — can shorten it or delete a protected version. Governance mode is the reversible, testable alternative.

open as a page

An S3 bucket had versioning enabled and later suspended. What does S3 do with new uploads and with the versions already stored, and why can that silently lose data?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

Suspending stops S3 from minting new version IDs: every later upload to a key is stored with the literal version ID null and overwrites the previous null version. Versions created while versioning was enabled are kept and still billed.

open as a page