バージョニングを有効化する

--versioning-configurationStatus=Enabledを指定する。

$ aws s3api put-bucket-versioning --bucket example-bucket --versioning-configuration Status=Enabled

成功しても出力はない。

有効化の直後は設定の伝播に時間がかかる。
伝播中は、有効化後に作成・更新したオブジェクトへのGETリクエストがHTTP 404 NoSuchKeyになる場合がある。
AWSのドキュメントでは、有効化から15分待ってから書き込みや削除に進むよう推奨している。

レプリケーションやObject Lockもバージョニングの有効化が前提である。

バージョニングの状態を確認する

get-bucket-versioningで現在の状態を確認する。

$ aws s3api get-bucket-versioning --bucket example-bucket
{
    "Status": "Enabled"
}

一度もバージョニングを設定していないバケットでは、レスポンス自体が空になる。

$ aws s3api get-bucket-versioning --bucket example-bucket
$

エラーではなく終了コードは0のため、出力の有無で判定する。

--query Status --output textで値だけを取り出す場合、未設定のバケットでは文字列Noneが返る。

$ aws s3api get-bucket-versioning --bucket example-bucket --query Status --output text
None

空文字列ではなくNoneが返るため、スクリプトで判定する際はNoneとの比較で書ける。

バージョンの一覧を確認する

バージョニングが有効なバケットでは、同じキーへ上書きするたびに新しいVersionIdが振られる。
list-object-versionsでキーごとの世代を確認する。

$ aws s3api list-object-versions --bucket example-bucket
{
    "Versions": [
        {
            "ETag": "\"e30260020baeb0398ff07b37dd33ed16\"",
            "ChecksumAlgorithm": [
                "CRC64NVME"
            ],
            "ChecksumType": "FULL_OBJECT",
            "Size": 3,
            "StorageClass": "STANDARD",
            "Key": "example.txt",
            "VersionId": "U5HvIlzkuLTVpDS9Xrk6m0lBFnrzGo9c",
            "IsLatest": true,
            "LastModified": "2026-08-01T03:05:16+00:00",
            "Owner": {
                "ID": "4997ce1a32cd1c916c78d870d4f4ddea30a6280177e5566c52014cdbb8841dfd"
            }
        },
        {
            "ETag": "\"4f98f59e877ecb84ff75ef0fab45bac5\"",
            "ChecksumAlgorithm": [
                "CRC64NVME"
            ],
            "ChecksumType": "FULL_OBJECT",
            "Size": 3,
            "StorageClass": "STANDARD",
            "Key": "example.txt",
            "VersionId": "CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq",
            "IsLatest": false,
            "LastModified": "2026-08-01T03:05:15+00:00",
            "Owner": {
                "ID": "4997ce1a32cd1c916c78d870d4f4ddea30a6280177e5566c52014cdbb8841dfd"
            }
        }
    ],
    "RequestCharged": null,
    "Prefix": ""
}

IsLatesttrueのバージョンがaws s3 cpget-objectで取得される現行バージョンである。

出力が長いため、必要な項目だけをテーブル形式で表示すると読みやすい。

$ aws s3api list-object-versions --bucket example-bucket --query 'Versions[].[Key,VersionId,IsLatest,LastModified]' --output table
-------------------------------------------------------------------------------------------
|                                   ListObjectVersions                                    |
+-------------+------------------------------------+--------+-----------------------------+
|  example.txt|  U5HvIlzkuLTVpDS9Xrk6m0lBFnrzGo9c  |  True  |  2026-08-01T03:05:16+00:00  |
|  example.txt|  CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq  |  False |  2026-08-01T03:05:15+00:00  |
+-------------+------------------------------------+--------+-----------------------------+

特定のキーだけを確認する場合は--prefixを指定する。

$ aws s3api list-object-versions --bucket example-bucket --prefix example.txt

過去のバージョンを取得する

get-object--version-idを指定すると、過去のバージョンをダウンロードできる。

$ aws s3api get-object --bucket example-bucket --key example.txt --version-id CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq old.txt
{
    "AcceptRanges": "bytes",
    "LastModified": "2026-08-01T03:05:15+00:00",
    "ContentLength": 3,
    "ETag": "\"4f98f59e877ecb84ff75ef0fab45bac5\"",
    "ChecksumCRC64NVME": "FfdqN7gXyuY=",
    "ChecksumType": "FULL_OBJECT",
    "VersionId": "CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq",
    "ContentType": "text/plain",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

aws s3 cpには--version-idがないため、バージョンを指定する場合はs3api get-objectを使う。

過去のバージョンを現行に戻す

誤った上書きから復旧する場合は、過去のバージョンを同じキーへコピーする。
--copy-sourceにバケット名とキーを渡し、?versionId=で戻したいバージョンを指定する。

$ aws s3api copy-object --bucket example-bucket --key example.txt \
    --copy-source "example-bucket/example.txt?versionId=CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq"
{
    "CopySourceVersionId": "CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq",
    "VersionId": "w8Rcri.XWN3IH7v7U0_ip5b6Y29WYd0Y",
    "ServerSideEncryption": "AES256",
    "CopyObjectResult": {
        "ETag": "\"4f98f59e877ecb84ff75ef0fab45bac5\"",
        "LastModified": "2026-08-01T03:36:44+00:00",
        "ChecksumType": "FULL_OBJECT",
        "ChecksumCRC64NVME": "FfdqN7gXyuY="
    }
}

コピーの結果は新しいバージョンとして追加され、コピー元のバージョンもそのまま残る。
古いバージョンを削除して戻すのではないため、復旧に失敗しても元の状態を保てる。

削除マーカーから復元する

バージョニングが有効なバケットでaws s3 rmを実行しても、実体は削除されない。
削除マーカーが最新バージョンとして追加され、通常の一覧やダウンロードから見えなくなるだけである。

$ aws s3 rm s3://example-bucket/example.txt
delete: s3://example-bucket/example.txt

$ aws s3 ls s3://example-bucket/
$

list-object-versionsで確認すると、削除マーカーがDeleteMarkersとして現れる(ETagなどは省略)。

{
    "Versions": [
        {
            "Key": "example.txt",
            "VersionId": "U5HvIlzkuLTVpDS9Xrk6m0lBFnrzGo9c",
            "IsLatest": false,
            "LastModified": "2026-08-01T03:05:16+00:00"
        },
        {
            "Key": "example.txt",
            "VersionId": "CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq",
            "IsLatest": false,
            "LastModified": "2026-08-01T03:05:15+00:00"
        }
    ],
    "DeleteMarkers": [
        {
            "Owner": {
                "ID": "4997ce1a32cd1c916c78d870d4f4ddea30a6280177e5566c52014cdbb8841dfd"
            },
            "Key": "example.txt",
            "VersionId": "ZIwg7QBTeA5IHnKDtyWEnYP3Bz4RhBNr",
            "IsLatest": true,
            "LastModified": "2026-08-01T03:05:17+00:00"
        }
    ],
    "RequestCharged": null,
    "Prefix": ""
}

削除マーカーのVersionIdを指定してdelete-objectを実行すると、直前の状態に戻る。

$ aws s3api delete-object --bucket example-bucket --key example.txt --version-id ZIwg7QBTeA5IHnKDtyWEnYP3Bz4RhBNr
{
    "DeleteMarker": true,
    "VersionId": "ZIwg7QBTeA5IHnKDtyWEnYP3Bz4RhBNr"
}
$ aws s3 ls s3://example-bucket/
2026-08-01 12:05:16          3 example.txt

なお--version-idに実体のバージョンを指定した場合、そのバージョンは完全に削除される。
復元目的で実行する際は、削除マーカーのVersionIdを指定しているかを確認する。

バケット全体を特定時点に戻したい場合は、 【AWS】s3-pit-restoreでS3を特定時点にまとめて復元する のようなツールを使うと、キーごとの復元作業をまとめて実行できる。

バージョニングを停止する

Status=Suspendedを指定するとバージョニングを停止できる。

$ aws s3api put-bucket-versioning --bucket example-bucket --versioning-configuration Status=Suspended

$ aws s3api get-bucket-versioning --bucket example-bucket
{
    "Status": "Suspended"
}

一度有効にしたバケットを未設定の状態には戻せない。
選べる値はEnabledSuspendedのみである。

停止後もすでに保存されたバージョンは残り続ける。
停止中に追加したオブジェクトはVersionIdnullになり、次の上書きでnullバージョンが置き換えられる。

停止した状態で同じキーへ2回上書きしてみる。

$ aws s3 cp example.txt s3://example-bucket/example.txt
upload: ./example.txt to s3://example-bucket/example.txt

$ aws s3 cp example.txt s3://example-bucket/example.txt
upload: ./example.txt to s3://example-bucket/example.txt

$ aws s3api list-object-versions --bucket example-bucket --query 'Versions[].[Key,VersionId,IsLatest]' --output table
--------------------------------------------------------------
|                     ListObjectVersions                     |
+--------------+------------------------------------+--------+
|  example.txt |  null                              |  True  |
|  example.txt |  U5HvIlzkuLTVpDS9Xrk6m0lBFnrzGo9c  |  False |
|  example.txt |  CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq  |  False |
+--------------+------------------------------------+--------+

2回上書きしてもnullバージョンは1つしか残らない。
停止中の上書きでは世代が増えないため、過去のバージョンは残るが新しい変更は保護されない。
ストレージコストを抑える目的であれば、停止よりも後述のライフサイクルルールでの世代管理が適している。

すべてのバケットの状態をまとめて確認する

list-bucketsと組み合わせると、アカウント内のバケットのバージョニング状態を一覧できる。

$ for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
    versioning=$(aws s3api get-bucket-versioning --bucket "$bucket" --query Status --output text 2>/dev/null)
    echo "$bucket ${versioning:-Denied}"
done
example-bucket Enabled
example-log-bucket None
example-static-bucket Suspended
example-deploy-bucket Enabled

未設定のバケットはNoneと表示される。
2>/dev/nullで捨てているのは権限不足などのエラーである。
エラー時は変数が空になるためDeniedと表示される。

zshでスクリプトを書く場合、statusは読み取り専用の変数のため代入に失敗する。
上記のように別の変数名を使う。

MFA Deleteを有効にする

MFA Deleteを有効にすると、バージョンの完全削除とバージョニング状態の変更にMFAの認証コードが必要になる。

有効化はバケットを作成したAWSアカウントのルートユーザーのみが実行でき、マネジメントコンソールからは設定できない。

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

--mfaにはMFAデバイスのシリアル番号と6桁のコードをスペース区切りで渡す。
仮想MFAデバイスの場合、シリアル番号はデバイスのARNである。

IAMユーザーの認証情報で実行するとAccessDeniedになる。

aws: [ERROR]: An error occurred (AccessDenied) when calling the PutBucketVersioning operation: Mfa Authentication must be used for this request

MFA Deleteを有効にしたバケットではライフサイクルルールを利用できないため、世代の自動削除と併用できない。

古いバージョンをライフサイクルで削除する

バージョニングを有効にすると、上書きのたびに古い世代が残り、ストレージコストが増え続ける。
各バージョンは差分ではなくオブジェクト全体を保持するため、3世代あれば3オブジェクト分の料金がかかる。
ライフサイクルルールで非現行バージョンの保持期間を決めておく。

{
  "Rules": [
    {
      "ID": "expire-noncurrent-versions",
      "Filter": {},
      "Status": "Enabled",
      "NoncurrentVersionExpiration": {
        "NoncurrentDays": 30
      },
      "Expiration": {
        "ExpiredObjectDeleteMarker": true
      }
    }
  ]
}
$ aws s3api put-bucket-lifecycle-configuration --bucket example-bucket --lifecycle-configuration file://lifecycle.json

NoncurrentVersionExpirationは現行でなくなってからの経過日数で古い世代を削除する。
ExpiredObjectDeleteMarkerは、対応する実体がなくなった削除マーカーを掃除する設定である。

設定内容はget-bucket-lifecycle-configurationで確認できる。

$ aws s3api get-bucket-lifecycle-configuration --bucket example-bucket
{
    "TransitionDefaultMinimumObjectSize": "all_storage_classes_128K",
    "Rules": [
        {
            "Expiration": {
                "ExpiredObjectDeleteMarker": true
            },
            "ID": "expire-noncurrent-versions",
            "Filter": {},
            "Status": "Enabled",
            "NoncurrentVersionExpiration": {
                "NoncurrentDays": 30
            }
        }
    ]
}

日数ではなく世代数で残したい場合はNewerNoncurrentVersionsを併用する。
1から100の範囲で指定でき、新しい順に指定した数の非現行バージョンを保持したうえで、それより古い世代をNoncurrentDaysの経過後に削除する。

"NoncurrentVersionExpiration": {
    "NoncurrentDays": 30,
    "NewerNoncurrentVersions": 3
}

すでにオブジェクトの有効期限ルールを設定しているバケットでバージョニングを有効にする場合は注意が必要である。
有効期限ルールは現行バージョンに削除マーカーを付けるだけになるため、非現行バージョンの有効期限ルールを追加しなければ古い世代が残り続ける。

バージョニングを有効にしたバケットを削除する

バージョンが残っているバケットは削除できない。
aws s3 rm --recursiveで見かけ上は空にしても、BucketNotEmptyのエラーになる。

$ aws s3 rm s3://example-bucket/ --recursive
delete: s3://example-bucket/example.txt

$ aws s3 rb s3://example-bucket
remove_bucket failed: s3://example-bucket An error occurred (BucketNotEmpty) when calling the DeleteBucket operation: The bucket you tried to delete is not empty. You must delete all versions in the bucket.

list-object-versionsの結果をdelete-objectsに渡し、バージョンと削除マーカーをまとめて削除する。

$ aws s3api delete-objects --bucket example-bucket \
    --delete "$(aws s3api list-object-versions --bucket example-bucket \
      --output json --query '{Objects: [Versions, DeleteMarkers][].{Key: Key, VersionId: VersionId}}')"
{
    "Deleted": [
        {
            "Key": "example.txt",
            "VersionId": "CHEtfwwY.F4LUL0sTcfMEKFxTc.90HBq"
        },
        {
            "Key": "example.txt",
            "VersionId": "U5HvIlzkuLTVpDS9Xrk6m0lBFnrzGo9c"
        },
        {
            "Key": "example.txt",
            "VersionId": "null",
            "DeleteMarker": true,
            "DeleteMarkerVersionId": "null"
        }
    ]
}

[Versions, DeleteMarkers][]で両方の配列を平坦化し、KeyVersionIdだけを取り出している。
3件目のVersionIdnullなのは、バージョニングを停止した状態で削除したためである。

delete-objectsは1回のリクエストで最大1000件までのため、オブジェクトが多いバケットでは分割して実行する。
| [:1000]で先頭1000件に絞り込み、結果が空になるまで繰り返す。

$ while true; do
    objects=$(aws s3api list-object-versions --bucket example-bucket \
      --query '{Objects: [Versions, DeleteMarkers][] | [:1000].{Key: Key, VersionId: VersionId}}')
    echo "$objects" | grep -q VersionId || break
    aws s3api delete-objects --bucket example-bucket --delete "$objects" > /dev/null
done

パイプを挟まず[Versions, DeleteMarkers][][:1000]と書くと、平坦化した配列ではなく各要素にスライスが適用され、結果が空になる。
| [:1000]のように書いて、平坦化の結果に対してスライスする。

バージョンをすべて削除するとバケットを削除できる。

$ aws s3 rb s3://example-bucket
remove_bucket: example-bucket

必要な権限

コマンドの実行にはそれぞれ次の権限が必要である。

コマンド必要なアクション
put-bucket-versionings3:PutBucketVersioning
get-bucket-versionings3:GetBucketVersioning
list-object-versionss3:ListBucketVersions
get-object –version-ids3:GetObjectVersion
copy-objects3:GetObjectVersion, s3:PutObject
delete-object –version-ids3:DeleteObjectVersion
delete-objectss3:DeleteObjectVersion
put-bucket-lifecycle-configurations3:PutLifecycleConfiguration
get-bucket-lifecycle-configurations3:GetLifecycleConfiguration
list-bucketss3:ListAllMyBuckets
s3 rbs3:DeleteBucket

AmazonS3ReadOnlyAccesss3:Get*s3:List*を含むため、状態や世代の確認だけであればマネージドポリシーで足りる。

参考