Enabling S3 Versioning on an Existing Bucket: Protect Files from Accidental Overwrites

You have an S3 bucket already holding production data, and one bad PUT or DELETE call could silently destroy a file forever. Enabling versioning is the surgical fix — it turns S3 into a time-machine for every object, without touching a single existing file.

TL;DR

AspectDetail
What changes for existing objects?Nothing. Pre-existing objects get a null version ID until they are overwritten.
What changes for new writes?Every PUT/DELETE creates a new version with a unique version ID.
Is it reversible?Versioning can be suspended, not deleted. Once enabled, it cannot be fully disabled.
Cost impact?You pay for storage of every version. Use Lifecycle rules to manage costs.
Required permissions3:PutBucketVersioning on the bucket.

How S3 Versioning Works

Think of an unversioned S3 bucket like a whiteboard — writing a new value erases the old one. Enabling versioning converts it into a stack of sticky notes: every write adds a new note on top, and the old ones remain underneath, retrievable by their unique version ID.

Real-world analogy: S3 versioning is like Google Docs revision history. The document you see is always the latest version, but every prior save is preserved and can be restored at any time. The difference: S3 charges you for every "revision" stored.

Version States Explained

stateDiagram-v2 [*] --> Unversioned : Bucket created Unversioned --> Enabled : PutBucketVersioning Status=Enabled Enabled --> Suspended : PutBucketVersioning Status=Suspended Suspended --> Enabled : PutBucketVersioning Status=Enabled
  1. Unversioned (default): Bucket starts here. Objects have no version ID.
  2. Versioning-Enabled: After you run the enable command. All new writes get a unique version ID. Existing objects retain a null version ID.
  3. Versioning-Suspended: New writes get a null version ID again, overwriting the previous null-versioned object. Existing versioned objects are preserved.

What Happens to Existing Objects?

sequenceDiagram participant Client participant S3 Note over S3: Versioning OFF Client->>S3: PUT report.csv S3-->>Client: Stored, versionId=null Note over S3: Versioning ENABLED Client->>S3: PUT report.csv (overwrite) S3-->>Client: New version abc123, null version preserved Client->>S3: DELETE report.csv (no versionId) S3-->>Client: Delete Marker inserted, all versions intact Client->>S3: GET report.csv?versionId=null S3-->>Client: Returns original pre-versioning object
  1. Before versioning: report.csv exists with no version ID.
  2. After enabling versioning: the object is untouched. Its implicit version ID is null.
  3. First overwrite after enabling: S3 preserves the null-versioned original and creates a new version with a real version ID (e.g., abc123).
  4. A DELETE without specifying a version ID inserts a Delete Marker — the object appears deleted but all versions remain recoverable.

Step-by-Step: Enable Versioning

Option 1 — AWS Console

  1. Open the S3 Console → select your bucket.
  2. Go to the Properties tab.
  3. Scroll to Bucket Versioning → click Edit.
  4. Select Enable → click Save changes.

Option 2 — AWS CLI

# Enable versioning on an existing bucket
aws s3api put-bucket-versioning \
  --bucket your-bucket-name \
  --versioning-configuration Status=Enabled
# Verify the versioning status
aws s3api get-bucket-versioning \
  --bucket your-bucket-name

Expected output:

{
    "Status": "Enabled"
}

Option 3 — AWS CloudFormation / IaC

🔽 Click to expand — CloudFormation snippet
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  MyVersionedBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: your-bucket-name
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: ExpireOldVersions
            Status: Enabled
            NoncurrentVersionExpiration:
              NoncurrentDays: 90

Option 4 — Terraform

🔽 Click to expand — Terraform snippet
resource "aws_s3_bucket" "example" {
  bucket = "your-bucket-name"
}

resource "aws_s3_bucket_versioning" "example" {
  bucket = aws_s3_bucket.example.id

  versioning_configuration {
    status = "Enabled"
  }
}

IAM: Least-Privilege Permission

The principal enabling versioning needs only one permission on the target bucket:

🔽 Click to expand — IAM Policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowEnableVersioning",
      "Effect": "Allow",
      "Action": "s3:PutBucketVersioning",
      "Resource": "arn:aws:s3:::your-bucket-name"
    }
  ]
}

Recovering a Previous Version

Once versioning is active, recovering an overwritten file is a two-step operation:

# Step 1: List all versions of an object
aws s3api list-object-versions \
  --bucket your-bucket-name \
  --prefix report.csv
# Step 2: Restore a specific version by copying it back as the latest
aws s3api copy-object \
  --bucket your-bucket-name \
  --copy-source "your-bucket-name/report.csv?versionId=abc123XYZ" \
  --key report.csv

Cost Control: Lifecycle Rules for Old Versions

Versioning without a Lifecycle policy is a silent cost leak. Every overwrite accumulates storage charges. Apply a NoncurrentVersionExpiration rule to automatically expire old versions after N days.

# Apply a lifecycle rule via CLI to expire noncurrent versions after 90 days
aws s3api put-bucket-lifecycle-configuration \
  --bucket your-bucket-name \
  --lifecycle-configuration '{
    "Rules": [
      {
        "ID": "ExpireOldVersions",
        "Status": "Enabled",
        "Filter": { "Prefix": "" },
        "NoncurrentVersionExpiration": {
          "NoncurrentDays": 90
        }
      }
    ]
  }'

Key Considerations & Gotchas

GotchaExplanation
MFA DeleteFor highest protection, enable MFA Delete to require multi-factor authentication before permanently deleting a version. Requires root credentials to configure.
Bucket replicationIf you use S3 Cross-Region Replication, versioning must be enabled on both source and destination buckets.
Delete Markers accumulateAdd a DeleteMarkerExpiration lifecycle rule to clean up expired delete markers automatically.
Suspension ≠ DeletionSuspending versioning stops creating new versions but does NOT delete existing ones. Storage costs continue.

Glossary

TermDefinition
Version IDA unique string S3 assigns to each object version when versioning is enabled.
null versionThe implicit version ID assigned to objects that existed before versioning was enabled.
Delete MarkerA placeholder object inserted by S3 when you delete a versioned object without specifying a version ID. Makes the object appear deleted without removing any version.
NoncurrentVersionExpirationA Lifecycle rule action that expires object versions that are no longer the current (latest) version.
MFA DeleteAn additional security layer requiring MFA authentication to permanently delete object versions or change versioning state.

Next Steps

  • 📖 Official Docs: Using versioning in S3 buckets — AWS Documentation
  • 🔒 Enable MFA Delete for buckets holding critical or compliance-sensitive data.
  • 💰 Set up a Lifecycle policy immediately after enabling versioning to cap storage costs.
  • 🔁 If you need cross-region durability, configure S3 Cross-Region Replication — versioning is a prerequisite.

Related Posts

Comments

Popular posts from this blog

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

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

EC2 SSH Connection Timeout: The Exact Security Group Rules You Need to Fix It