RDS DB instances should have deletion protection enabled
An accidental aws rds delete-db-instance call or a misguided Terraform destroy can wipe a production database in seconds. Deletion protection acts as a safety latch: the API rejects any delete request until the flag is explicitly turned off, giving teams time to catch mistakes before data is lost.
Backups and snapshots help with recovery, but restoring from a snapshot still causes downtime and risks data loss for transactions between the last snapshot and the deletion event. Deletion protection costs nothing and removes the single-click failure mode entirely.
Retrofit consideration
Enabling deletion protection on a running instance requires a modify operation that applies immediately but causes no downtime or reboot. If your Terraform state already has deletion_protection = false (or unset), applying the change may surprise CI pipelines that run terraform destroy as part of ephemeral environment teardown.
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 "rds" {
source = "soc2.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "hipaa.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nist80053.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nistcsf.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nist800171.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nydfs23.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "ffiec.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "rbiitfnbfc.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "fedramplow.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
module "rds" {
source = "nist80053rev4.compliance.tf/terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
}
If you use terraform-aws-modules/rds/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 "rds" {
source = "terraform-aws-modules/rds/aws"
version = ">=7.0.0"
allocated_storage = 20
db_name = "myapp"
db_subnet_group_name = "example-db-subnet-group"
engine = "mysql"
engine_version = "8.0.41"
family = "mysql8.0"
identifier = "abc123"
instance_class = "db.t3.micro"
major_engine_version = "8.0"
password_wo = "change-me-in-production"
skip_final_snapshot = true
username = "dbadmin"
vpc_security_group_ids = ["sg-12345678"]
deletion_protection = true
}
Use AWS provider resources directly. See docs for the resources involved: aws_db_instance.
resource "aws_db_instance" "this" {
allocated_storage = 20
enabled_cloudwatch_logs_exports = ["general", "slowquery"]
engine = "mysql"
identifier = "pofix-abc123"
instance_class = "db.t3.micro"
monitoring_interval = 60
monitoring_role_arn = "arn:aws:iam::123456789012:role/example-role"
password = "ChangeMe123!"
skip_final_snapshot = true
username = "dbadmin"
deletion_protection = true
}
What this control checks
This control checks that every aws_db_instance resource has deletion_protection explicitly set to true. The argument defaults to false when omitted, so any instance without it fails. At plan time, the control evaluates the planned value in each aws_db_instance block: true passes, false or absent fails. For Aurora instances managed through aws_rds_cluster, deletion protection is a cluster-level argument covered by a separate control. This control applies to standalone and Multi-AZ RDS instances only.
Common pitfalls
Default is false when omitted
The
deletion_protectionargument onaws_db_instancedefaults tofalse. Omitting it entirely does not enable protection. Every instance definition must includedeletion_protection = trueexplicitly.Terraform destroy blocked by deletion protection
terraform destroyfails with anInvalidParameterCombinationerror against any instance withdeletion_protection = true. The fix is to set it tofalse, apply, then destroy. Ephemeral environments often use a variable to toggle deletion protection off, but that variable must default totruefor production, otherwise the control is satisfied in CI and silently absent where it matters.Aurora clusters vs standalone instances
For Aurora,
deletion_protectionis an argument onaws_rds_cluster, not on the individualaws_rds_cluster_instanceresources. This control targetsaws_db_instance(standalone RDS). If your fleet includes Aurora clusters, ensure the corresponding cluster-level control is also enforced.Read replicas inherit nothing
Read replicas created with
replicate_source_dbdon't inheritdeletion_protectionfrom the source instance. Set it explicitly on each replica.
Audit evidence
Auditors expect deletion protection enabled across all in-scope RDS instances. The most direct artifact is the AWS Config rule rds-instance-deletion-protection-enabled showing compliant evaluations for each instance. A screenshot of the RDS console "Configuration" tab showing "Deletion protection: Enabled," or aws rds describe-db-instances output with DeletionProtection: true for every instance, also works. CloudTrail logs for ModifyDBInstance events setting DeletionProtection to true show when the control was applied and by whom.
Framework-specific interpretation
SOC 2: Supports the SOC 2 Trust Services Criteria for Availability and Security, particularly the CC7 series around change management and prevention of unauthorized system changes. A single misconfigured terraform destroy that removes a production database is exactly the kind of availability incident CC7 controls are meant to prevent.
HIPAA Omnibus Rule 2013: HIPAA requires covered entities to implement safeguards protecting the availability and integrity of ePHI under 45 CFR 164.312(a)(1) and 164.312(c)(1). Deletion protection directly addresses unauthorized or accidental destruction of RDS instances that may hold ePHI, and is one concrete way to demonstrate those safeguards are in place.
NIST SP 800-53 Rev 5: AC-3, CM-5, and SI-7(1) all have a preventive, integrity-focused character that deletion protection supports by reducing the risk of unauthorized or accidental destructive actions against database infrastructure. It also complements CP-9 by lowering the likelihood that backup recovery is needed in the first place.
NIST Cybersecurity Framework v2.0: PR.DS in the Protect function covers data security across the full data lifecycle. Deletion protection is a direct implementation of that goal: without it, a single API call can permanently remove a production database.
FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, deletion protection is best treated as a preventive safeguard supporting access control and configuration change objectives, while complementing CP-9 backup and recovery expectations. It reduces the chance of accidental or unauthorized database destruction in federal workloads.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
rds_db_instance_deletion_protection_enabledAWS Config Managed Rule:
RDS_INSTANCE_DELETION_PROTECTION_ENABLEDCheckov Check:
CKV_AWS_293Powerpipe Control:
aws_compliance.control.rds_db_instance_deletion_protection_enabledProwler Check:
rds_instance_deletion_protectionAWS Security Hub Control:
RDS.8Trivy Check:
AWS-0177
Last reviewed: 2026-03-09