Skip to content

DynamoDB tables should have point-in-time recovery enabled

Without PITR, recovering from accidental deletes or application bugs that corrupt table data depends entirely on manual on-demand backups taken before the incident. If no recent backup exists, the data is permanently lost. PITR costs approximately $0.20 per GB-month based on table size and runs continuously with zero performance impact.

The 35-day rolling window with per-second granularity is far more useful than periodic on-demand snapshots. For tables powering transactional workloads or storing session data, the cost is negligible compared to the business impact of irrecoverable data loss. Enabling PITR is a one-line Terraform change with no downtime.

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 "dynamodb_table" {
  source  = "soc2.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "hipaa.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nist80053.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nistcsf.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "fedrampmoderate.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "cisv80ig1.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nist800171.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "cisacyberessentials.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nydfs23.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "ffiec.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "acscessentialeight.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "eugmpannex11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "cfrpart11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "rbicybersecurity.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "rbiitfnbfc.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "fedramplow.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "hipaasecurity2003.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nistcsfv11.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "nist80053rev4.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

module "dynamodb_table" {
  source  = "pcidssv321.compliance.tf/terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"
}

If you use terraform-aws-modules/dynamodb-table/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 "dynamodb_table" {
  source  = "terraform-aws-modules/dynamodb-table/aws"
  version = ">=5.0.0"

  attributes = [
    {
      name = "id"
      type = "S"
    }
  ]
  hash_key = "id"
  name     = "abc123"

  point_in_time_recovery_enabled = true
}

Use AWS provider resources directly. See docs for the resources involved: aws_dynamodb_table.

resource "aws_dynamodb_table" "this" {
  attribute {
    name = "id"
    type = "S"
  }

  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "id"
  name         = "pofix-abc123"

  point_in_time_recovery {
    enabled = true
  }
}

What this control checks

This control checks that every aws_dynamodb_table resource includes a point_in_time_recovery block with enabled set to true. Omitting the block fails for the same reason: DynamoDB defaults PITR to disabled. It also fails when enabled is explicitly false. Toggling the argument on existing tables doesn't trigger a resource replacement or cause downtime. Terraform only needs dynamodb:UpdateContinuousBackups to apply the change.

Common pitfalls

  • Omitting the block defaults to disabled

    Declare an aws_dynamodb_table without a point_in_time_recovery block and DynamoDB defaults to PITR disabled. Terraform won't detect drift on this setting if the block was never present in configuration, so the table silently remains unprotected.

  • Global table replicas need PITR enabled per replica

    PITR doesn't propagate automatically from one replica to others in a global table. Each regional replica needs its own configuration, and that configuration belongs at the top level of the aws_dynamodb_table resource, not nested inside the replica block. Check every region independently.

  • Restored tables lose associated configuration

    PITR restores data to a new table, not in-place. Auto-scaling policies, IAM resource policies, tags, CloudWatch alarms, and DynamoDB Streams configuration are not copied to the restored table. Plan automation to reapply these settings after a restore using aws dynamodb tag-resource and related API calls.

  • Tables created outside Terraform are invisible

    DynamoDB tables created by AWS Amplify, AppSync, or other services aren't managed by Terraform and will fail this control. Either import them with terraform import aws_dynamodb_table.name table_name or enable PITR directly via aws dynamodb update-continuous-backups --table-name <name> --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true.

Audit evidence

AWS Config rule evaluation results for the managed rule dynamodb-pitr-enabled showing all tables as COMPLIANT are the primary audit artifact. The DynamoDB console Backups tab, showing "Point-in-time recovery" as "Enabled" with the earliest and latest restorable timestamps, works as direct visual evidence. CloudTrail logs with UpdateContinuousBackups calls where PointInTimeRecoverySpecification.PointInTimeRecoveryEnabled is true establish when PITR was activated and by whom.

For continuous assurance, Config conformance pack reports or scan output from Prowler or Steampipe showing passing status across all in-scope tables demonstrates ongoing coverage rather than a snapshot check.

Framework-specific interpretation

SOC 2: SOC 2 Availability criteria expect demonstrable backup and recovery capability. PITR's 35-day window with per-second granularity directly addresses that for DynamoDB, and the Config rule result is what examiners ask to see.

PCI DSS v4.0: For tables storing payment-related data, PITR gives you the precise restore capability PCI DSS expects for incident response and business continuity. Per-second granularity matters when you need to restore to a known-good state before a corruption or unauthorized modification event.

HIPAA Omnibus Rule 2013: 45 CFR 164.308(a)(7)(ii)(A) requires covered entities to create and maintain retrievable exact copies of ePHI as part of the contingency plan standard. PITR on DynamoDB tables containing ePHI satisfies the data backup component of that requirement.

NIST SP 800-53 Rev 5: Continuous PITR backups address CP-9's requirement that system backup exists for user-level data. The 35-day restoration window with per-second granularity directly supports CP-10's recovery and reconstitution objectives.

NIST Cybersecurity Framework v2.0: The Protect and Recover functions both benefit from PITR. Continuous backups address PR.DS data security outcomes; the ability to restore to any second within 35 days covers RC.RP recovery planning for data assets affected by a cybersecurity event.

FedRAMP Moderate Baseline Rev 4: CP-9 and CP-10 require information system backup at defined frequencies and tested restoration procedures. PITR covers both: continuous backups with per-second granularity satisfy the backup frequency requirement, and the 35-day window gives the recovery capability CP-10 calls for.

Tool mappings

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

  • Compliance.tf Control: dynamodb_table_point_in_time_recovery_enabled

  • AWS Config Managed Rule: DYNAMODB_PITR_ENABLED

  • Checkov Checks: CKV_AWS_165, CKV_AWS_28

  • Powerpipe Control: aws_compliance.control.dynamodb_table_point_in_time_recovery_enabled

  • Prowler Check: dynamodb_tables_pitr_enabled

  • AWS Security Hub Control: DynamoDB.2

  • KICS Query: 741f1291-47ac-4a85-a07b-3d32a9d6bd3e

  • Trivy Check: AWS-0024

Last reviewed: 2026-03-09