Skip to content

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 PutBucketVersioning with Status: 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 standalone aws_s3_bucket_versioning resource in provider v4+. The two paths conflict, and Terraform may silently ignore one of them. Drop the inline block entirely and use aws_s3_bucket_versioning consistently.

  • Storage costs grow without lifecycle rules

    Add a noncurrent_version_expiration rule in aws_s3_bucket_lifecycle_configuration for 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 an AccessDenied error. 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: s3_bucket_versioning_enabled

  • AWS Config Managed Rule: S3_BUCKET_VERSIONING_ENABLED

  • Checkov Check: CKV_AWS_21

  • Powerpipe Control: aws_compliance.control.s3_bucket_versioning_enabled

  • Prowler Check: s3_bucket_object_versioning

  • AWS Security Hub Control: S3.14

  • KICS Query: 568a4d22-3517-44a6-a7ad-6a7eed88722c

  • Trivy Check: AWS-0090

Last reviewed: 2026-03-09