Back to blog
AWSBest PracticesCloud SecurityDatabasesReliability

RDS Deletion Protection Disabled: Why It Matters and How to Fix It

Learn why RDS deletion protection matters, how a single command can wipe a production database, and how to enable and enforce it with CLI, Terraform, and policy-as-code.

TL;DR

This check flags RDS instances running without deletion protection, which means a single API call or console click can wipe a production database. Enable it with aws rds modify-db-instance --deletion-protection and enforce it in your Terraform.

Deletion protection is one of those settings that does nothing until the day it saves your job. An RDS instance without it sits one fat-fingered command away from deletion, and unlike an EC2 instance you can spin back up, a deleted database can mean lost data, broken applications, and a very long night reconstructing state from backups (if you have them).

The Lensix rds_deletion_protection check inspects every RDS instance in your account and reports any where deletion protection is turned off. It is a small flag with outsized consequences, so it is worth fixing everywhere it applies.


What this check detects

RDS instances expose a property called DeletionProtection. When set to true, AWS refuses to delete the instance through the API, CLI, or console until you explicitly disable the flag first. This check looks at each DB instance and flags any where that property is false.

You can confirm the current state of a specific instance yourself:

aws rds describe-db-instances \
  --db-instance-identifier my-prod-db \
  --query 'DBInstances[0].DeletionProtection'

A return value of false means the instance is exposed. true means deletion is blocked until the flag is removed.

Note: Deletion protection guards against deleting the instance, not against modifying it, scaling it down, or rebooting it. It also does not stop someone from deleting the underlying snapshot or the whole CloudFormation stack if that stack manages the resource lifecycle. Think of it as a seatbelt, not a force field.


Why it matters

Databases are stateful, and that changes the blast radius of a mistake. Here are the scenarios this check is designed to head off.

Accidental deletion

The most common cause of a lost database is a human running the wrong command against the wrong account. Someone meant to delete my-staging-db and tabbed into the production terminal. Someone ran a cleanup script with a loose name filter. Someone clicked the red button in the console and skim-read the confirmation dialog. Deletion protection turns all of these into a harmless error message instead of a recovery operation.

Infrastructure-as-code drift and replacements

Terraform and CloudFormation will happily destroy and recreate a database if a change forces replacement. Renaming an instance, changing certain immutable parameters, or running terraform destroy against the wrong workspace can all queue a deletion. Deletion protection causes the apply to fail loudly rather than silently tearing down production data.

Compromised or overly broad credentials

If an attacker or a misconfigured automation role gets rds:DeleteDBInstance permissions, deletion protection forces an extra deliberate step. It is not a substitute for least-privilege IAM, but it adds friction at exactly the moment friction is useful.

Warning: Deletion protection does not protect data on its own. If delete-automated-backups is set and you eventually do delete the instance, the automated backups go with it. Always pair deletion protection with a tested backup and snapshot retention strategy.


How to fix it

Enabling deletion protection is a non-disruptive change. It applies immediately and does not require a reboot or maintenance window.

Using the AWS CLI

aws rds modify-db-instance \
  --db-instance-identifier my-prod-db \
  --deletion-protection \
  --apply-immediately

For an Aurora cluster, the flag lives on the cluster rather than the individual instances:

aws rds modify-db-cluster \
  --db-cluster-identifier my-prod-cluster \
  --deletion-protection \
  --apply-immediately

Verify the change took effect:

aws rds describe-db-instances \
  --db-instance-identifier my-prod-db \
  --query 'DBInstances[0].DeletionProtection'

Using the AWS Console

  1. Open the RDS console and select Databases.
  2. Choose the instance, then click Modify.
  3. Scroll to the Deletion protection section and check Enable deletion protection.
  4. Click Continue, choose Apply immediately, and confirm.

Fixing it at the source in Terraform

If your databases are managed by Terraform, do not patch them out of band. Change the code so the next apply enforces the setting and the drift does not come back.

resource "aws_db_instance" "prod" {
  identifier          = "my-prod-db"
  engine              = "postgres"
  instance_class      = "db.t3.medium"
  allocated_storage   = 100

  deletion_protection = true

  # Belt and suspenders for accidental teardowns
  skip_final_snapshot       = false
  final_snapshot_identifier = "my-prod-db-final"
}

For Aurora, set it on the cluster resource:

resource "aws_rds_cluster" "prod" {
  cluster_identifier  = "my-prod-cluster"
  engine              = "aurora-postgresql"
  deletion_protection = true
}

CloudFormation

{
  "Type": "AWS::RDS::DBInstance",
  "Properties": {
    "DBInstanceIdentifier": "my-prod-db",
    "Engine": "postgres",
    "DeletionProtection": true
  }
}

Tip: Add DeletionPolicy: Retain and UpdateReplacePolicy: Retain to the CloudFormation resource as well. Deletion protection blocks direct deletes, but these policies stop the database from being removed when the stack itself is deleted or the resource is replaced.

Danger: If you ever genuinely need to delete an instance, you must disable deletion protection first. Before you do, double-check the instance identifier and the account you are operating in. Disabling protection and deleting in two quick commands removes the exact safety margin this check exists to provide.


How to prevent it from happening again

Fixing the instances you have today is half the work. The other half is making sure new databases never ship without protection.

Gate it in CI with policy-as-code

If you use Terraform, run a policy check against the plan before it applies. Here is an Open Policy Agent (Conftest) rule that fails any RDS instance without deletion protection:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "aws_db_instance"
  resource.change.after.deletion_protection == false
  msg := sprintf("RDS instance '%s' must have deletion_protection enabled", [resource.address])
}

Wire this into your pipeline so a non-compliant plan never reaches apply:

terraform plan -out=tfplan
terraform show -json tfplan > plan.json
conftest test plan.json

Use a Service Control Policy for org-wide enforcement

An SCP can deny the deletion of instances outright unless an explicit condition is met, giving you a guardrail no individual account can bypass. At minimum, scope rds:DeleteDBInstance tightly so only a small break-glass role can perform it.

Continuous detection

Drift happens. Someone disables protection for a one-off migration and forgets to turn it back on. Lensix re-runs the rds_deletion_protection check on a schedule so the gap surfaces within hours instead of being discovered during an incident. Pair the finding with an alert to your team channel so it gets eyes quickly.

Tip: AWS Config has a managed rule, rds-instance-deletion-protection-enabled, that you can deploy across an organization. Use it alongside Lensix if you want native remediation hooks, but treat continuous scanning as the source of truth rather than a one-time audit.


Best practices

  • Enable it on every production and staging database. The cost is zero and the protection is real. Reserve disabling it for short-lived, genuinely disposable instances.
  • Never disable protection from a script. If automation needs to delete a database, make that a deliberate, reviewed, manual step.
  • Combine it with final snapshots. Set skip_final_snapshot = false so that even an intentional deletion leaves you a restore point.
  • Lock down delete permissions. Deletion protection is a backstop, not a replacement for least-privilege IAM on rds:DeleteDBInstance and rds:DeleteDBCluster.
  • Protect the lifecycle in IaC. Use prevent_destroy in Terraform or retain policies in CloudFormation so the database survives a bad plan or stack deletion.
  • Test your restore path. Protection buys you time, but only a verified, recent backup actually recovers your data. Run restore drills.

Deletion protection is cheap insurance. Turn it on everywhere it belongs, enforce it in code, and let continuous scanning catch the day it slips. The five minutes you spend now is far less than the night you would otherwise spend rebuilding a database from scratch.

Fix RDS Deletion Protection Disabled on AWS | Lensix | Lensix