DevDocsDev Docs
S3

S3 Versioning

Complete guide to object versioning, MFA delete, and version management

S3 versioning enables you to keep multiple variants of an object in the same bucket. This protects against accidental deletions and overwrites.

Understanding Versioning

Key Concepts

  • Each object version has a unique Version ID
  • Versioning is enabled at the bucket level
  • Once enabled, versioning cannot be disabled—only suspended
  • All versions are stored and billed as separate objects

Versioning States

A bucket can be in one of three versioning states:

StateDescriptionBehavior
UnversionedDefault stateNo version IDs, overwrites replace objects
Versioning-EnabledVersioning activeEach PUT creates new version
Versioning-SuspendedVersioning pausedNew objects get null version ID

You cannot return a bucket to unversioned state after enabling versioning.

Enabling Versioning

Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-bucket \
  --versioning-configuration Status=Enabled
Check versioning status
aws s3api get-bucket-versioning --bucket my-bucket

Output:

{
  "Status": "Enabled",
  "MFADelete": "Disabled"
}

How Versioning Works

Initial Upload

When you upload an object, S3 assigns a unique Version ID:

Upload first version
aws s3 cp report.pdf s3://my-bucket/

# Object now has Version ID: v1ABC123

Subsequent Uploads

Each upload creates a new version; previous versions are preserved:

Upload updated version
aws s3 cp report-v2.pdf s3://my-bucket/report.pdf

# Creates new version: v2DEF456
# Previous version v1ABC123 still exists

Accessing Versions

By default, GET returns the latest version:

Get latest version
aws s3 cp s3://my-bucket/report.pdf ./

To get a specific version:

Get specific version
aws s3api get-object \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123 \
  report-old.pdf

Listing Object Versions

List all versions
aws s3api list-object-versions --bucket my-bucket
Example output
{
  "Versions": [
    {
      "Key": "report.pdf",
      "VersionId": "v2DEF456",
      "IsLatest": true,
      "LastModified": "2024-01-15T10:30:00Z",
      "Size": 2048576
    },
    {
      "Key": "report.pdf",
      "VersionId": "v1ABC123",
      "IsLatest": false,
      "LastModified": "2024-01-10T09:00:00Z",
      "Size": 1024000
    }
  ],
  "DeleteMarkers": []
}
List versions for prefix
aws s3api list-object-versions \
  --bucket my-bucket \
  --prefix reports/
List versions of specific object
aws s3api list-object-versions \
  --bucket my-bucket \
  --prefix report.pdf \
  --query 'Versions[?Key==`report.pdf`]'
Paginated listing
aws s3api list-object-versions \
  --bucket my-bucket \
  --max-keys 100 \
  --key-marker "" \
  --version-id-marker ""

Delete Operations with Versioning

Important

Deleting an object in a versioned bucket doesn't remove it permanently—it creates a delete marker.

Delete Markers

When you delete an object without specifying a version ID:

Simple delete (creates delete marker)
aws s3 rm s3://my-bucket/report.pdf

This creates a delete marker that becomes the current version. The object appears deleted, but all previous versions still exist.

Restoring Deleted Objects

Find and remove delete marker
# Find the delete marker
aws s3api list-object-versions \
  --bucket my-bucket \
  --prefix report.pdf \
  --query 'DeleteMarkers[?IsLatest==`true`].{Key:Key,VersionId:VersionId}'

# Delete the delete marker
aws s3api delete-object \
  --bucket my-bucket \
  --key report.pdf \
  --version-id "DeleteMarkerVersionId123"
Copy previous version to restore
aws s3api copy-object \
  --bucket my-bucket \
  --key report.pdf \
  --copy-source "my-bucket/report.pdf?versionId=v1ABC123"

Permanent Deletion

To permanently delete a specific version:

Delete specific version
aws s3api delete-object \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123

Permanently deleting versions cannot be undone. Use MFA Delete for additional protection.

MFA Delete

MFA Delete provides an additional layer of security for version deletion.

MFA Delete Protection

When enabled, MFA Delete requires:

  • Multi-factor authentication to permanently delete object versions
  • MFA to change the bucket's versioning state

Enable MFA Delete

MFA Delete can only be enabled by the root account using the AWS CLI:

Enable MFA Delete
aws s3api put-bucket-versioning \
  --bucket my-bucket \
  --versioning-configuration Status=Enabled,MFADelete=Enabled \
  --mfa "arn:aws:iam::123456789012:mfa/root-account-mfa-device 123456"

Root Account Required

MFA Delete can only be enabled/disabled by the root account, not by IAM users.

Delete with MFA

Delete version with MFA
aws s3api delete-object \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123 \
  --mfa "arn:aws:iam::123456789012:mfa/root-account-mfa-device 654321"

Versioning with Lifecycle Rules

Combine versioning with lifecycle rules to manage costs:

Delete noncurrent versions after 90 days
{
  "Rules": [
    {
      "ID": "ExpireOldVersions",
      "Status": "Enabled",
      "Filter": {},
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 90
      }
    }
  ]
}
Clean up incomplete uploads
{
  "Rules": [
    {
      "ID": "AbortIncompleteUploads",
      "Status": "Enabled",
      "Filter": {},
      "AbortIncompleteMultipartUpload": {
        "DaysAfterInitiation": 7
      }
    }
  ]
}
Archive noncurrent versions to Glacier
{
  "Rules": [
    {
      "ID": "ArchiveOldVersions",
      "Status": "Enabled",
      "Filter": {},
      "NoncurrentVersionTransitions": [
        {
          "NoncurrentDays": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "NoncurrentDays": 90,
          "StorageClass": "GLACIER"
        }
      ],
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 365
      }
    }
  ]
}
Apply lifecycle configuration
aws s3api put-bucket-lifecycle-configuration \
  --bucket my-bucket \
  --lifecycle-configuration file://lifecycle.json

Keep Only N Versions

Limit the number of noncurrent versions retained:

Keep last 3 versions
{
  "Rules": [
    {
      "ID": "KeepLast3Versions",
      "Status": "Enabled",
      "Filter": {},
      "NoncurrentVersionExpiration": {
        "NewerNoncurrentVersions": 3,
        "NoncurrentDays": 1
      }
    }
  ]
}

This keeps only the 3 most recent noncurrent versions and deletes older ones after 1 day of becoming noncurrent.

Expired Delete Markers

When all versions of an object are deleted (only a delete marker remains), you can automatically clean up:

Remove expired delete markers
{
  "Rules": [
    {
      "ID": "DeleteExpiredMarkers",
      "Status": "Enabled",
      "Filter": {},
      "Expiration": {
        "ExpiredObjectDeleteMarker": true
      }
    }
  ]
}

Suspending Versioning

Suspending versioning doesn't delete existing versions—it just stops creating new ones.

Suspend versioning
aws s3api put-bucket-versioning \
  --bucket my-bucket \
  --versioning-configuration Status=Suspended

When suspended:

  • Existing versions remain intact
  • New object uploads get a null Version ID
  • Uploading overwrites objects with null Version ID

Version-Specific Operations

Get version metadata
aws s3api head-object \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123
Set ACL on version
aws s3api put-object-acl \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123 \
  --acl private
Tag specific version
aws s3api put-object-tagging \
  --bucket my-bucket \
  --key report.pdf \
  --version-id v1ABC123 \
  --tagging 'TagSet=[{Key=Status,Value=Archived}]'

Presigned URLs for Versions

Generate presigned URLs for specific versions:

Presigned URL for version
aws s3 presign \
  s3://my-bucket/report.pdf \
  --version-id v1ABC123 \
  --expires-in 3600

Versioning with Replication

Cross-region replication (CRR) and same-region replication (SRR) require versioning enabled on both source and destination buckets.

Replication with delete marker sync
{
  "Role": "arn:aws:iam::123456789012:role/replication-role",
  "Rules": [
    {
      "ID": "ReplicateAll",
      "Status": "Enabled",
      "Priority": 1,
      "Filter": {},
      "DeleteMarkerReplication": {
        "Status": "Enabled"
      },
      "Destination": {
        "Bucket": "arn:aws:s3:::destination-bucket",
        "ReplicaModifications": {
          "Status": "Enabled"
        }
      }
    }
  ]
}

Common Use Cases

Backup and Recovery

Script: Backup all versions of critical files
#!/bin/bash
BUCKET="my-bucket"
BACKUP_BUCKET="my-backup-bucket"
PREFIX="critical/"

aws s3api list-object-versions --bucket $BUCKET --prefix $PREFIX \
  --query 'Versions[*].[Key,VersionId]' --output text | \
  while read KEY VERSION; do
    aws s3api copy-object \
      --bucket $BACKUP_BUCKET \
      --key "${KEY}/${VERSION}" \
      --copy-source "${BUCKET}/${KEY}?versionId=${VERSION}"
  done

Audit Trail

Use versioning combined with Object Lock for compliance:

Enable Object Lock (new buckets only)
aws s3api create-bucket \
  --bucket my-compliance-bucket \
  --object-lock-enabled-for-bucket \
  --region us-east-1

Cost Considerations

Storage Costs

Each version is a complete copy and is billed separately:

  • 100 versions of a 1GB file = 100GB billed storage
  • Noncurrent versions count toward storage totals
  • Delete markers are not charged

Cost optimization strategies:

  1. Lifecycle rules: Transition old versions to cheaper storage classes
  2. Version limits: Keep only N recent noncurrent versions
  3. Clean up delete markers: Remove expired delete markers
  4. Monitor: Use S3 Storage Lens to track version storage

Best Practices

Versioning Best Practices

  1. Enable on important buckets - Protection against accidental deletion
  2. Combine with lifecycle rules - Manage costs by expiring old versions
  3. Enable MFA Delete - Protect against malicious deletion
  4. Monitor storage growth - Versions add up quickly
  5. Use for compliance - Pair with Object Lock for WORM
  6. Replicate versions - Enable replication for DR
  7. Document retention policies - Know how many versions to keep

Next Steps

On this page