S3 buckets should have versioning enabled
Without versioning, a single overwrite or delete permanently destroys the previous state of an object. There is no recycle bin, no undo. Enabling versioning gives you a recoverable history for every object in the bucket, which directly supports disaster recovery, ransomware response, and accidental deletion rollback.
Versioning is also a prerequisite for S3 Cross-Region Replication and S3 Object Lock. If you plan to use either feature, versioning must already be on. The storage cost of retaining old versions is real, but lifecycle rules on aws_s3_bucket_lifecycle_configuration can expire noncurrent versions after a defined period, keeping costs manageable.
Retrofit consideration
Enabling versioning on existing buckets is irreversible. Once enabled, you can only suspend it, never fully disable it. Suspended buckets still retain all historical versions, and storage costs for those versions persist until explicitly deleted via lifecycle rules or manual cleanup.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "s3_bucket" {
source = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaa.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsf.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist800171.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "awscontroltower.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "awswellarchitected.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nydfs23.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisv140.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "ffiec.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "acscessentialeight.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "acscism2023.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "eugmpannex11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "awsgenai.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedramplow.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053rev4.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
If you use terraform-aws-modules/s3-bucket/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
versioning = {
enabled = "Enabled"
}
}
Use AWS provider resources directly. See docs for the resources involved: aws_s3_bucket_versioning.
resource "aws_s3_bucket" "this" {
bucket = "pofix-abc123"
force_destroy = true
}
resource "aws_s3_bucket_versioning" "this" {
bucket = "example-bucket-abc123"
versioning_configuration {
status = "Enabled"
}
}
What this control checks
The policy checks that each S3 bucket has versioning explicitly enabled. In Terraform AWS provider v4+, versioning is configured through a standalone aws_s3_bucket_versioning resource, not an inline block on aws_s3_bucket.
To pass, declare an aws_s3_bucket_versioning resource referencing the target bucket with versioning_configuration { status = "Enabled" }. It fails if no aws_s3_bucket_versioning resource is attached, or if status is "Suspended". The mfa_delete argument is optional and does not affect pass/fail for this control, though it adds protection against version deletion.
Common pitfalls
Versioning cannot be fully disabled after enablement
Once you call
PutBucketVersioningwithStatus: Enabled, there is no going back. You can suspend versioning, which stops creating new versions and assigns null version IDs to new objects, but you cannot return to the unversioned state. Existing versions stay in the bucket indefinitely. Factor this in before enabling across all buckets at once.Legacy inline versioning block can cause drift
Perpetual Terraform drift is the most common symptom of mixing the old
versioning { enabled = true }inline block with the standaloneaws_s3_bucket_versioningresource in provider v4+. The two paths conflict, and Terraform may silently ignore one of them. Drop the inline block entirely and useaws_s3_bucket_versioningconsistently.Storage costs grow without lifecycle rules
Add a
noncurrent_version_expirationrule inaws_s3_bucket_lifecycle_configurationfor any high-churn bucket. Without it, every overwrite and delete accumulates a stored version that S3 bills for indefinitely. On active buckets this adds up fast.MFA Delete requires root credentials to enable
Setting
mfa_delete = "Enabled"requires the request to be signed by the root account with an MFA device. Terraform runs as an IAM role or user, not root, so attempting to set this in Terraform produces anAccessDeniederror. Enable MFA Delete manually via the CLI with root credentials instead.Suspended status is not the same as Enabled
status = "Suspended"is not equivalent to"Enabled". Suspended versioning preserves existing versions but stops creating new ones for incoming writes. This control fails on suspended buckets.
Audit evidence
Auditors expect to see the AWS Config rule s3-bucket-versioning-enabled returning compliant for every in-scope bucket. A screenshot or export from the S3 console showing 'Bucket Versioning: Enabled' under the Properties tab works as direct confirmation. For large environments, a Config aggregator compliance report filtered to this rule across all accounts and regions is the most practical artifact.
CloudTrail entries for PutBucketVersioning establish when versioning was enabled and by whom, which is useful for change management reviews.
Framework-specific interpretation
SOC 2: The Availability and Processing Integrity criteria both ask for demonstrated controls around data recoverability and protection from unauthorized change. Versioning satisfies the practical question examiners ask: can you restore an object to a known-good prior state without relying on an external backup?
PCI DSS v4.0: Versioning can support evidence for Requirement 10.2.1 by showing object-level change history and demonstrating recoverability, but it is not a substitute for the access and event logging controls that Requirement 10 actually mandates.
HIPAA Omnibus Rule 2013: The HIPAA Security Rule's addressable implementation specification for data backup expects covered entities to protect ePHI integrity. Versioning provides a recoverable object history, so an accidental or malicious overwrite does not permanently destroy the prior state of ePHI stored in S3, supporting the requirements at 45 CFR 164.312(c)(1) and 164.312(d).
NIST SP 800-53 Rev 5: CP-9 calls for backups that include user-level and system-level information. Versioning delivers that at the object level. SI-7 asks for mechanisms to detect and reverse unauthorized changes, and retaining prior versions directly supports both the detection and reversal sides of that control.
NIST Cybersecurity Framework v2.0: S3 versioning covers two CSF v2 functions at once: PR.DS (protecting data integrity by retaining prior object states) and RC.RP (supporting recovery without depending on external backup infrastructure).
FedRAMP Moderate Baseline Rev 4: CP-9 and CP-10 require the ability to back up and recover information following a disruption. Versioning satisfies the object-level recovery piece of that without requiring a separate backup service for data already in S3.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_bucket_versioning_enabledAWS Config Managed Rule:
S3_BUCKET_VERSIONING_ENABLEDCheckov Check:
CKV_AWS_21Powerpipe Control:
aws_compliance.control.s3_bucket_versioning_enabledProwler Check:
s3_bucket_object_versioningAWS Security Hub Control:
S3.14KICS Query:
568a4d22-3517-44a6-a7ad-6a7eed88722cTrivy Check:
AWS-0090
Last reviewed: 2026-03-09