Recovering Deleted S3 Files: A Step-by-Step Guide Using S3 Versioning

Accidentally deleting a critical file from S3 is a common operational nightmare — but if you had S3 Versioning enabled on the bucket, that file is not gone. It's hidden behind a Delete Marker, and recovery is a deterministic, reversible operation.

TL;DR

StepActionTool
1Confirm versioning is enabled on the bucketConsole / CLI
2List all versions to find the Delete MarkerCLI / Console
3Identify the last valid version ID before deletionCLI / Console
4Delete the Delete Marker to restore the objectCLI / Console
5Verify the object is accessible againCLI / Console

How S3 Versioning Works Under the Hood

When versioning is enabled, S3 never truly overwrites or deletes an object. Every PUT creates a new version. A DELETE request (without specifying a version ID) does not remove any data — it inserts a special object called a Delete Marker as the current version. This marker causes S3 to return a 404 Not Found for standard GET requests, making the object appear deleted to applications.

Analogy: Think of S3 Versioning like a filing cabinet where you never throw away documents — you just place a red "REMOVED" sticky note on top of the stack. The file is still there; you just need to peel off the sticky note to see it again.

Architecture: What Happens During a Delete and Restore

sequenceDiagram participant Client as "Client / Application" participant S3 as "Amazon S3" Note over Client,S3: Phase 1 — Normal Read Client->>S3: GET object (no version ID) S3-->>Client: 200 OK (serves latest version) Note over Client,S3: Phase 2 — Accidental Delete Client->>S3: DELETE object (no version ID) S3-->>S3: Inserts Delete Marker as current version S3-->>Client: 204 No Content (marker created) Note over Client,S3: Phase 3 — Object Appears Gone Client->>S3: GET object (no version ID) S3-->>Client: 404 Not Found (Delete Marker is current version) Note over Client,S3: Phase 4 — Restore by Removing Delete Marker Client->>S3: DELETE object (with Delete Marker version ID) S3-->>S3: Permanently removes Delete Marker S3-->>Client: 204 No Content (marker deleted) Note over Client,S3: Phase 5 — Object Restored Client->>S3: GET object (no version ID) S3-->>Client: 200 OK (previous version is now current)
  1. Normal GET: The client requests the object. S3 serves the current (latest) version.
  2. Unversioned DELETE: The client sends a DELETE without a version ID. S3 inserts a Delete Marker as the new current version. No data is removed.
  3. GET after DELETE: S3 sees the Delete Marker as the current version and returns 404 Not Found to the client.
  4. Restore (Delete the Delete Marker): The operator explicitly deletes the Delete Marker by specifying its version ID. S3 removes the marker, promoting the previous real version back to current.
  5. GET after Restore: S3 now serves the restored object version successfully.

Step-by-Step Recovery Guide

Step 1: Confirm Versioning Is Enabled

Before anything else, verify the bucket has versioning in an Enabled state (not Suspended).

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

Expected output:

{
    "Status": "Enabled"
}

If the output is empty or shows Suspended, versioning was not active when the delete occurred, and the data is unrecoverable via this method.

Step 2: List All Versions of the Deleted Object

Use list-object-versions to see every version and every Delete Marker for the specific key.

aws s3api list-object-versions \
  --bucket your-bucket-name \
  --prefix "path/to/your-file.txt"
🔽 [Click to expand] — Example output
{
    "Versions": [
        {
            "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
            "Size": 1024,
            "StorageClass": "STANDARD",
            "Key": "path/to/your-file.txt",
            "VersionId": "abc123XYZ_previous_version",
            "IsLatest": false,
            "LastModified": "2024-01-10T10:00:00.000Z"
        }
    ],
    "DeleteMarkers": [
        {
            "Owner": { "DisplayName": "your-account" },
            "Key": "path/to/your-file.txt",
            "VersionId": "def456UVW_delete_marker",
            "IsLatest": true,
            "LastModified": "2024-01-15T14:30:00.000Z"
        }
    ]
}

From this output, identify two critical values:

  • Delete Marker Version ID (IsLatest: true in DeleteMarkers): def456UVW_delete_marker
  • Last Valid Version ID (IsLatest: false in Versions): abc123XYZ_previous_version

Step 3: Restore by Deleting the Delete Marker

To restore the object, you must permanently delete the Delete Marker by specifying its exact VersionId. This is a permanent action on the marker itself.

aws s3api delete-object \
  --bucket your-bucket-name \
  --key "path/to/your-file.txt" \
  --version-id "def456UVW_delete_marker"

A successful response returns the version ID of the deleted marker and a DeleteMarker: true flag, confirming you removed a marker (not actual data).

Step 4: Verify the Object Is Restored

aws s3api head-object \
  --bucket your-bucket-name \
  --key "path/to/your-file.txt"

A 200 OK response with object metadata confirms the file is accessible again. The previous version is now the current version.

Alternative: Restore a Specific Older Version

If you need to restore a version that is not the most recent one (e.g., you want to roll back two versions), copy that specific version over itself. This creates a new current version with the content of the old one.

aws s3api copy-object \
  --bucket your-bucket-name \
  --copy-source "your-bucket-name/path/to/your-file.txt?versionId=abc123XYZ_previous_version" \
  --key "path/to/your-file.txt"

This is the safest restore pattern — it is non-destructive and creates an auditable version history.

Doing This in the AWS Console

graph TD A["Open S3 Bucket in Console"] --> B["Navigate to Object's Folder"] B --> C["Toggle 'Show versions' ON"] C --> D["Locate file with 'Delete marker' badge"] D --> E["Select the Delete Marker checkbox"] E --> F["Click Delete"] F --> G["Type 'permanently delete' to confirm"] G --> H["Delete Marker Removed"] H --> I["Previous Version is Now Current"] I --> J["Verify: Object is accessible again"]
  1. Navigate to your S3 bucket in the AWS Console.
  2. Browse to the folder containing the deleted file.
  3. Toggle "Show versions" in the top-right of the object list. Delete Markers will appear with a "Delete marker" badge.
  4. Select the Delete Marker for your file and click Delete.
  5. Confirm the permanent deletion of the marker. The object is now restored.

IAM Permissions Required

The operator performing the restore needs the following minimum permissions. Note that deleting a specific version (including a Delete Marker) requires s3:DeleteObjectVersion, which is a separate and more privileged action than s3:DeleteObject.

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

Prevention: Best Practices Going Forward

  • Enable MFA Delete: Requires multi-factor authentication to permanently delete object versions or change versioning state. This prevents accidental or malicious permanent deletions.
  • S3 Object Lock: For compliance-critical data, use Object Lock in GOVERNANCE or COMPLIANCE mode to make objects immutable for a defined retention period.
  • S3 Lifecycle Policies: Configure lifecycle rules to automatically expire old versions after a defined period to manage storage costs from accumulated versions.
  • Bucket Policies: Restrict s3:DeleteObjectVersion to a break-glass IAM role to minimize the blast radius of accidental permanent deletions.

Glossary

TermDefinition
Delete MarkerA placeholder object inserted by S3 when a versioned object is deleted without specifying a version ID. It has no data and causes GET requests to return 404.
Version IDA unique string assigned by S3 to each version of an object when versioning is enabled.
MFA DeleteAn S3 bucket feature that requires MFA authentication to permanently delete object versions or suspend versioning.
S3 Object LockA feature that prevents object versions from being deleted or overwritten for a fixed amount of time or indefinitely.
IsLatestA boolean field in the list-object-versions API response indicating whether a version is the current (most recent) version of an object.

Next Steps

Enable versioning on all production S3 buckets today — it is a zero-cost configuration change that provides a critical safety net. Review the official AWS documentation for S3 Versioning and MFA Delete to harden your data protection posture further.

Comments

Popular posts from this blog

IAM User vs. IAM Role: Why Your EC2 Instance Should Never Use a User

EC2 No Internet Access in Custom VPC: Attaching an Internet Gateway and Fixing Route Tables

Lambda Infinite Loop with S3: How to Prevent Recursive Triggers