S3 objects accidentally deleted — recover from versioning
A production object/key range disappears after a cleanup script
Focused incident mode — strips away everything but this runbook.
Symptoms
- A production object/key range disappears after a cleanup script
- Website/asset references 404 where they returned 200 yesterday
- Lifecycle rule unexpectedly expired a prefix
Recovery speed is decided years before the incident: versioning-enabled buckets are recoverable in minutes, everything else depends on what backups exist. Time spent arguing with a lifecycle rule is time data stays lost.
Evidence to grab
Collect these artifacts before you change anything — they prove (or rule out) each cause.
Versioning state
aws s3api get-bucket-versioning --bucket <BUCKET>Object versions and delete markers
aws s3api list-object-versions --bucket <BUCKET> --prefix <KEY>
Diagnostic flow
Run the steps in order, marking each claim confirmed or refuted. The evidence you gather feeds the ranked causes below.
Guided decision tree is ready.
Steps with commands, expected output, and pass/fail branches.
Ranked root causes
Evidence checklist
- The key 404s but versions exist in list-object-versions
- A DeleteMarker is the newest entry
Confirm before fixing
aws s3api list-object-versions --bucket <BUCKET> --prefix <KEY> --query 'DeleteMarkers[0]'Confirm the marker
Fix
aws s3api delete-object --bucket <BUCKET> --key <KEY> --version-id <MARKER-VERSION-ID>Remove the delete marker to restore the last version (no copy needed)
Resolution runbook
Apply from top to bottom after the cause is confirmed. Review every command before running it — risk tags are shown on each.
aws s3api get-bucket-versioning --bucket <BUCKET>Confirm versioning
aws s3api list-object-versions --bucket <BUCKET> --prefix <KEY>Enumerate available versions
aws s3api copy-object --bucket <BUCKET> --key <RESTORED-KEY> --copy-source "<BUCKET>/<KEY>?versionId=<GOOD-VERSION-ID>"Copy the known-good version to a restore target
Expected: ETag returned
aws s3api delete-object --bucket <BUCKET> --key <KEY> --version-id <DELETE-MARKER-VERSION-ID>If restoring in place over a versioned bucket, remove the marker after copying the version
Prevention
- Enable versioning + object lock on every bucket holding user data.
- Add a retention rule for delete markers, and bulletproof the deletion script with dry-runs.
- Back up unversioned buckets with S3 Replication or a cross-account sync.
Related runbooks
Same stack, same failure family — worth a skim.
All client types get 403 Forbidden on a bucket
Browser upload/download fails with AccessDenied immediately
A role in account A cannot read a bucket owned by account B
Commands marked READ CHANGE DESTRUCTIVE — never run DESTRUCTIVE commands against production data you cannot restore. Copy → verify → execute.