Recovering Deleted S3 Files: How to Restore Previous Versions with S3 Versioning

You ran a script, hit delete in the console, or an application bug wiped an S3 object — and now it's gone. If S3 Versioning was enabled on that bucket before the deletion occurred, the object is almost certainly recoverable. This guide walks through exactly how S3 Versioning protects your data, how delete markers work, and the precise CLI steps to restore a previous version.

TL;DR: Recovering Deleted S3 Files at a Glance

StepActionWhat It Does
1Confirm versioning is enabledVerifies recovery is possible
2List object versionsFinds the delete marker and prior versions
3Delete the delete markerMakes the previous version current again
4Verify restorationConfirms the object is accessible

Concept Foundation: How S3 Versioning Protects Objects

S3 Versioning is a bucket-level feature that retains every version of every object stored in the bucket. When enabled, S3 assigns a unique Version ID to each PUT operation. Rather than overwriting the previous object, S3 stores both versions simultaneously. The most recently written version is the current version; all prior versions are non-current versions.

Three versioning states exist at the bucket level:

  • Unversioned (default) — No version IDs are assigned. Deletes are permanent.
  • Versioning-enabled — All objects receive version IDs. Deletes create a delete marker.
  • Versioning-suspended — New objects receive a null version ID. Existing versioned objects are retained.

Understanding the delete marker is the key to recovery. When you issue a plain DELETE request (without specifying a Version ID) against a versioning-enabled bucket, S3 does not remove any data. Instead, it inserts a delete marker — a zero-byte placeholder object with its own Version ID — and makes that marker the current version. Any subsequent GET request for the object returns a 404, because the current version is a delete marker, not real data. The previous versions remain intact in storage.

sequenceDiagram participant Client participant S3 Client->>S3: PUT object (no version specified) S3-->>Client: 200 OK, VersionId: v1 Client->>S3: PUT object (updated content) S3-->>Client: 200 OK, VersionId: v2 (v1 becomes non-current) Client->>S3: DELETE object (no VersionId) S3-->>Client: 204 No Content, DeleteMarker VersionId: dm Note over S3: v1 and v2 still exist as non-current versions Client->>S3: GET object (no VersionId) S3-->>Client: 404 Not Found (current version is delete marker) Client->>S3: DELETE object, VersionId: dm S3-->>Client: 204 No Content (delete marker removed) Client->>S3: GET object (no VersionId) S3-->>Client: 200 OK, VersionId: v2 (restored)
  1. PUT v1: Object is written; S3 assigns Version ID v1. This is the current version.
  2. PUT v2: A second write assigns Version ID v2. Version v1 becomes non-current.
  3. DELETE (no Version ID): S3 inserts a delete marker (dm) as the new current version. Both v1 and v2 are retained as non-current versions.
  4. GET (no Version ID): S3 returns HTTP 404 because the current version is a delete marker.
  5. DELETE the delete marker: Removing dm by its Version ID promotes v2 back to current. The object is accessible again.

Depth Signal: The Null Version ID Edge Case

A non-obvious behavior emerges when an object was first uploaded to a bucket before versioning was enabled. That object carries a null version ID — S3's placeholder for objects that predate versioning on the bucket. When you delete such an object in a versioning-enabled bucket without specifying a Version ID, S3 creates a delete marker (with a real Version ID) and retains the null-version object as a non-current version. The null-version object is not permanently deleted. Recovery follows the same procedure: delete the delete marker by its Version ID, and the null-version object becomes current again. This surprises engineers who assume pre-versioning objects are unprotected once versioning is turned on.

Step 1: Confirm S3 Versioning Is Enabled on the Bucket

Before attempting recovery, verify the bucket's versioning state. If versioning was never enabled, the deletion was permanent and no version history exists.

aws s3api get-bucket-versioning \
  --bucket your-bucket-name

Operator Rationale: This call returns the Status field. A value of Enabled confirms version history is present. A missing Status field or Suspended means recovery may be partial or impossible for recently deleted objects.

Expected output when versioning is active:

{
    "Status": "Enabled"
}

Step 2: List All Versions and Locate the Delete Marker

With versioning confirmed, list all versions of the deleted object. This returns both the data versions and any delete markers.

aws s3api list-object-versions \
  --bucket your-bucket-name \
  --prefix your-object-key

Operator Rationale: The --prefix flag scopes the response to the specific object key. Without it, the command returns all versions for all objects in the bucket — potentially a very large response. The output contains two arrays: Versions (actual data) and DeleteMarkers. You need the VersionId of the delete marker and the VersionId of the version you want to restore.

Example output (abbreviated):

{
    "Versions": [
        {
            "Key": "your-object-key",
            "VersionId": "abc123ExampleVersionId",
            "IsLatest": false,
            "LastModified": "2024-01-15T10:30:00.000Z",
            "Size": 204800
        }
    ],
    "DeleteMarkers": [
        {
            "Key": "your-object-key",
            "VersionId": "xyz789ExampleDeleteMarkerId",
            "IsLatest": true,
            "LastModified": "2024-01-16T08:00:00.000Z"
        }
    ]
}

The delete marker has "IsLatest": true — that is the object blocking your GET requests. Note both Version IDs before proceeding.

Step 3: Remove the Delete Marker to Restore the Previous Version

Deleting a delete marker requires specifying its exact Version ID. This is a targeted DELETE on the marker itself, not on the data.

aws s3api delete-object \
  --bucket your-bucket-name \
  --key your-object-key \
  --version-id xyz789ExampleDeleteMarkerId

Operator Rationale: When you supply a --version-id that corresponds to a delete marker, S3 permanently removes that marker. The most recent data version — abc123ExampleVersionId in this example — automatically becomes the current version. No copy or re-upload is required. This is the minimal, non-destructive recovery path.

Think of the delete marker as a sticky note placed on top of a filing cabinet drawer. The files inside are untouched. Removing the sticky note reveals everything underneath exactly as it was left.

Step 4: Verify the Object Is Accessible

Confirm the restored version is now the current object and is readable.

aws s3api head-object \
  --bucket your-bucket-name \
  --key your-object-key

Operator Rationale: A successful head-object response (HTTP 200) with a populated VersionId and ContentLength confirms the object is current and accessible. A 404 response means the delete marker was not removed correctly — re-check the Version ID used in Step 3.

Alternative: Restore a Specific Older Version

If the version immediately before the delete marker is not the one you want — for example, you need a version from three writes ago — copy the specific version back to the same key. This makes it the new current version without deleting any existing versions.

aws s3api copy-object \
  --bucket your-bucket-name \
  --copy-source your-bucket-name/your-object-key?versionId=abc123ExampleVersionId \
  --key your-object-key

Operator Rationale: The copy-object call reads the specified version and writes it as a new PUT, generating a fresh Version ID. The previous versions remain in the bucket as non-current. This approach is additive — it does not delete anything — making it the safer choice when you are uncertain which version is correct.

Experience Signal: The 404 That Wasn't a Missing File

A common misdiagnosis: an application starts returning 404 for an S3 object, and the team assumes the file was never uploaded or the key is wrong. The console shows no object at that path. Engineers spend time checking upload pipelines and application logs before realizing the bucket has versioning enabled.

Running list-object-versions reveals a delete marker sitting at IsLatest: true, placed there by an automated cleanup script that issued a plain DELETE without a Version ID. The actual file — fully intact — is one step below the marker. Removing the delete marker resolves the 404 in seconds. The fix is not in the upload pipeline; it is in understanding that a versioned DELETE is never a true deletion.

IAM Permissions Required for Recovery

The recovery steps above require specific S3 permissions. Apply least privilege — scope these permissions to the specific bucket and key prefix where possible.

🔽 Click to expand — IAM policy for S3 version recovery
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3VersionRecovery",
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketVersioning",
        "s3:ListBucketVersions",
        "s3:GetObjectVersion",
        "s3:DeleteObjectVersion",
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::your-bucket-name",
        "arn:aws:s3:::your-bucket-name/*"
      ]
    }
  ]
}

s3:DeleteObjectVersion is required to remove the delete marker. Without it, Step 3 will fail with an AccessDenied error even if the principal has s3:DeleteObject. These are distinct IAM actions.

flowchart TD A["Object appears deleted / 404"] --> B{"Versioning enabled
on bucket?"} B -- No --> C["Permanent deletion
Recovery not possible"] B -- Yes --> D["aws s3api list-object-versions
--prefix your-object-key"] D --> E{"Delete marker
present?"} E -- Yes --> F["aws s3api delete-object
--version-id <marker-id>"] F --> G["Previous version
becomes current"] E -- No --> H["Locate desired version
in Versions array"] H --> I["aws s3api copy-object
--copy-source bucket/key?versionId=..."] I --> G G --> J{"Correct version
restored?"} J -- Yes --> K["aws s3api head-object
Verify 200 OK"] J -- No --> H
  1. Versioning enabled? — If No, deletion was permanent. Recovery is not possible through versioning.
  2. List versions — Identify whether a delete marker exists and locate prior data versions.
  3. Delete marker present? — If Yes, remove it to restore the previous version. If No, the object may still exist as a non-current version; copy it to make it current.
  4. Correct version? — If the restored version is not the right one, use copy-object with the specific Version ID to promote an older version.
  5. Verify — Confirm with head-object that the object is accessible.

Recovering Deleted S3 Files: Wrap-Up and Next Steps

Recovering deleted S3 files when versioning is enabled reduces to a single operation in most cases: delete the delete marker by its Version ID. The data was never removed — S3 simply placed a marker in front of it. The steps above cover the standard recovery path, the null-version edge case, and the IAM permissions required to execute each action.

To prevent future recovery scenarios from becoming incidents, consider these follow-on actions:

  • Enable S3 Object Lock in Governance or Compliance mode for objects that must not be deleted.
  • Configure S3 Lifecycle rules to transition non-current versions to cheaper storage tiers (S3-IA, Glacier) and expire them after a defined retention window.
  • Enable S3 server access logging or AWS CloudTrail data events for the bucket to audit which principal issued the DELETE and when.
  • Review the official S3 Versioning documentation for bucket-level configuration details.

Glossary

TermDefinition
Version IDA unique string S3 assigns to each object version when bucket versioning is enabled.
Delete MarkerA zero-byte placeholder object S3 inserts when a versioned object is deleted without specifying a Version ID. It makes the object appear deleted to standard GET requests.
Non-current VersionAny object version that is not the most recently written version. Retained in storage until explicitly deleted or expired by a Lifecycle rule.
Null Version IDThe Version ID assigned to objects that existed in a bucket before versioning was enabled on that bucket.
s3:DeleteObjectVersionThe IAM action required to permanently delete a specific version of an S3 object, including delete markers.

Related Posts

Comments

Popular posts from this blog

EC2 No Internet Access in Custom VPC: Fix Internet Gateway and Route Table

EC2 SSH Connection Timeout: Which Security Group Rules to Check

Difference Between IAM User and IAM Role: Which One Should Your EC2 Use?