Set Deletion Protection
Sets the provider's deletion-protection attribute to true on RDS instances, Aurora clusters, DynamoDB tables and load balancers.
When to use this rule
Use this when: Your modules declare databases, tables or load balancers without setting deletion protection at all, and you want the protection enforced by AWS rather than only by Terraform.
Do not use this when: You run ephemeral environments that are destroyed after each run. Destroying a protected resource takes two applies: one to turn the attribute off, one to destroy.
Why this rule exists
These services offer deletion protection as an attribute AWS itself enforces, and the upstream modules leave it unset unless every caller remembers the input. Once set, a terraform destroy, a console click and an API call are all refused until someone turns it off first.
It is a stronger guard than prevent_destroy, which only Terraform honours, and it can be set on the resource without a fork only if the module exposes the input and the caller sets it.
Related Terraform and OpenTofu issues
| Repository | Issue | Title |
|---|---|---|
| hashicorp/terraform | #22544 | "Variables may not be used here" for prevent_destroy |
| hashicorp/terraform | #18367 | Feature request: support prevent_destroy for modules |
| hashicorp/terraform | #24746 | Ability to use locals or wildcards in ignore_changes lifecycle block |
| hashicorp/terraform | #24188 | Support for dynamic blocks and meta-arguments |
| hashicorp/terraform | #27360 | A method to override configuration and meta arguments within a module |
| opentofu/opentofu | #1329 | Support variables in lifecycle blocks |
Affected resources
| Resource | Service | Why |
|---|---|---|
aws_db_instance | Amazon RDS | deletion_protection, left unset by default |
aws_rds_cluster | Amazon Aurora | deletion_protection, left unset by default |
aws_dynamodb_table | Amazon DynamoDB | deletion_protection_enabled, left unset by default |
aws_lb | Elastic Load Balancing | enable_deletion_protection, left unset by default |
What this rule does
Sets a provider attribute on each matching resource, one attribute name per resource type:
| Resource | Attribute set |
|---|---|
aws_db_instance | deletion_protection = true |
aws_rds_cluster | deletion_protection = true |
aws_dynamodb_table | deletion_protection_enabled = true |
aws_lb | enable_deletion_protection = true |
A resource that leaves the attribute unset gains it. A resource that already sets it, to a literal or to a module variable, keeps what it has: an explicit value in the module wins, and the rule reports not_applicable for it. Unlike a lifecycle argument, this is a provider attribute, so once set the protection lives in AWS and applies to every client, not only to Terraform.
Before and after
Before (upstream module):
resource "aws_db_instance" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Set Deletion Protection applied):
resource "aws_db_instance" "this" {
# ... resource configuration ...
deletion_protection = true
tags = var.tags
}The attribute name differs per resource type (see "What this rule does" above); the shape of the change is the same for each.
The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.
Real-world scenario
A cleanup script deleted a load balancer by ARN, straight through the API. Deletion protection would have refused the call regardless of what Terraform knew.
Known limits
- The four resource types are fixed for this rule and cannot be retargeted by a parameter.
- Only a resource that leaves the attribute unset is changed. The upstream terraform-aws-modules for RDS, Aurora and DynamoDB already expose it as a module input (
deletion_protection,deletion_protection_enabled), and an explicit value in the module wins, so on those modules the rule reportsnot_applicableand the input is the place to set it. - Sets the attribute only. It does not add
prevent_destroy; uselifecycle_prevent_destroy_datafor the Terraform-side guard as well. - Does not protect the data inside the resource, only the resource itself.
- Only
.tfand.tofufiles are read. Modules written in JSON syntax (.tf.json,.tofu.json) are left untouched. - Only the module's own directory and its first-level
modules/*submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
The rule's definition
The HCL this rule is written in - its parameters, their shipped defaults, and the transformers it runs - is published at resource_set_deletion_protection, alongside every other selectable rule.
How to enable
On a framework host, per request:
Add ?rules=resource_set_deletion_protection to your HTTPS module source, using the id exactly as written.
module "example" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=resource_set_deletion_protection"
}A ?rules= list is either all bare names, which replace your organization's rules for that download, or all - prefixed names, which subtract from them. Mixing the two is refused with a 400 rather than silently treated as a replacement. To add this rule on top of your organization's rules instead, use ?add_rules=resource_set_deletion_protection; a bare ?rules= list cannot be combined with it, because a bare list already replaced the set there was something to add to.
The + add prefix was removed. It reached the registry only when written %2B, and a literal + in a query string decodes to a space everywhere, so ?rules=+resource_set_deletion_protection arrived as a bare name with a leading space — one step from silently replacing your organization's rules. Any +, in either spelling, is now a 400 naming ?add_rules=.
On your organization's host:
Add the rule to the organization's Baseline, which applies to every module served from the bare organization form <alias>.compliance.tf/<namespace>/<name>/<provider>, or to a named ruleset that one module selects with ?ruleset=<name> on the HTTPS form of that address. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead: there the rule goes into the config draft and reaches the environment by promotion. On both organization forms ?rules= and ?add_rules= are refused with a 400 — including the subtractive - prefix — rather than dropped, so you cannot mistake an ignored override for an applied one. Change the configuration, not the URL. See Baseline rules and rulesets for the configuration flow, and registry resolution for how the address forms differ.
Break-glass path
On a framework host, add ?rules=-resource_set_deletion_protection to the module source URL and run terraform init -upgrade to fetch a copy without this rule. The copy in .terraform/modules/ stays until the next upgrade, so remove the override when you are done. On your organization's host the parameter is refused with a 400 rather than ignored. The rule comes from the organization's configuration: a Baseline rule applies to every module served from the bare organization form, so an admin disables it for the duration of the operation, or keeps it out of the Baseline and in a ruleset that only the modules needing it select. On a project environment the posture is the bound snapshot and its waivers. See Baseline rules and rulesets.
Failure modes
| Scenario | Result |
|---|---|
| Rule targets a module with no matching resource types | No-op. Module is delivered unchanged and the manifest records not_applicable. |
| The module already sets the attribute, from a literal or a variable | Left as is and recorded as not_applicable. Set the module's own input instead. |
Someone runs terraform destroy on a protected resource | AWS refuses the deletion and Terraform reports the API error. Set the attribute to false, apply, then destroy. |
Terraform and OpenTofu compatible
This rule works with both Terraform (1.x+) and OpenTofu (1.6+). It reads ordinary HCL, and anything it changes stays ordinary HCL, so the module behaves the same under either tool.
Help us improve this page
Operational Rules are a new feature. We'd love your feedback on this rule page — what's useful, what's missing, what's confusing. Share feedback.