Create Before Destroy
Adds create_before_destroy = true to security groups and ACM certificates. The replacement is created first; the old resource is destroyed after.
When to use this rule
Use this when: Your modules manage security groups or ACM certificates that stay attached while Terraform replaces them: a rule change that forces a new security group, or a certificate whose domain list changes.
Do not use this when: The resource carries a fixed name. With create-first ordering the replacement exists while the old one still does, so an explicit unique name collides; use name_prefix or let the provider generate the name.
Why this rule exists
A security group that is still attached to a network interface cannot be deleted, so a change that forces replacement fails halfway through the apply. A certificate still bound to a listener has the same problem, with an outage while the listener has nothing to serve.
create_before_destroy is Terraform's own answer: create the new resource, move the references, then destroy the old one. It is a lifecycle argument, so it cannot be passed into an upstream module as a variable.
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_security_group | Amazon VPC | Still attached to a network interface, instance or load balancer when a change forces replacement |
aws_acm_certificate | AWS Certificate Manager | Still bound to a listener or distribution when a change forces replacement |
What this rule does
Adds a lifecycle { create_before_destroy = true } block to each matching resource (aws_security_group, aws_acm_certificate). When a change forces replacement, Terraform and OpenTofu create the new resource first and destroy the old one only after the replacement exists, so anything still attached to the old one keeps working through the swap. A resource that already carries a lifecycle block keeps it; the argument is merged in.
Before and after
Before (upstream module):
resource "aws_security_group" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Create Before Destroy applied):
resource "aws_security_group" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
create_before_destroy = true
}
}The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.
Real-world scenario
A security group rule change forced a replacement while the group was still attached to a running instance. Without create-first ordering the apply failed after the old group was already scheduled for deletion.
Known limits
- Only
aws_security_groupandaws_acm_certificateby default. Other resource types need theresource_typesparameter set by your organization. - Does not change what forces a replacement. The provider decides that; the rule changes only the order of create and destroy.
- 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.
Default configuration
These are the values the rule ships with. They are not settable from a module source URL: ?rules= carries rule names only, so a per-request enable uses exactly the defaults below. Your organization sets them per rule, in its Baseline or in a ruleset, from the Operational Rules page; see Baseline rules and rulesets.
| Parameter | Type | Default | Description |
|---|---|---|---|
resource_types | list(string) | ["aws_security_group", "aws_acm_certificate"] | Resource types to replace create-first |
The rule's definition
The HCL this rule is written in - its parameters, their shipped defaults, and the transformers it runs - is published at lifecycle_create_before_destroy, alongside every other selectable rule.
How to enable
On a framework host, per request:
Add ?rules=lifecycle_create_before_destroy 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=lifecycle_create_before_destroy"
}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=lifecycle_create_before_destroy; 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=+lifecycle_create_before_destroy 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=-lifecycle_create_before_destroy 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. |
A change forces replacement of a resource with a fixed name | The apply fails on a name conflict because both resources exist at once. Switch the module input to name_prefix. |
Another resource references the security group in its security_groups list | Terraform updates the reference to the new group before destroying the old one, which is the point of the ordering. |
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.