terraform taint and -replace
Sometimes a resource needs to be destroyed and recreated even when its configuration hasn’t changed — for example, a corrupted EC2 instance, a misconfigured security group, or a certificate that needs rotation. Terraform provides two ways to force this: the classic terraform taint command (soft-deprecated) and the modern -replace flag.
The Modern Approach: -replace Flag (Terraform 0.15.2+)
The recommended way to force recreation is the -replace flag on plan and apply:
# Preview what will be replacedterraform plan -replace=aws_instance.web
# Force recreation — no taint step neededterraform apply -replace=aws_instance.web
# Replace multiple resourcesterraform apply -replace=aws_instance.web -replace=aws_eip.web_ipThis is safer than taint because:
- The replacement happens in a single atomic
applyoperation - You can preview exactly what changes with
-replaceinplanbefore committing - No intermediate “tainted” state in the state file
The Classic Approach: terraform taint
terraform taint marks a resource as “tainted” in the state file, forcing Terraform to destroy and recreate it on the next apply. The resource continues to exist until apply runs.
# Mark a resource for recreationterraform taint aws_instance.web
# Multiple resourcesterraform taint aws_instance.webterraform taint aws_security_group.web
# Taint a resource inside a moduleterraform taint module.networking.aws_nat_gateway.main
# Taint a count-indexed resourceterraform taint 'aws_instance.app[2]'
# Taint a for_each resourceterraform taint 'aws_instance.app["web-server-1"]'Then on next apply:
# aws_instance.web is tainted, so must be replaced-/+ resource "aws_instance" "web" { ~ id = "i-old123" -> (known after apply) }Untainting a Resource
# Remove the taint marking without destroying the resourceterraform untaint aws_instance.webWhen to Force Resource Recreation
| Scenario | Use -replace or taint |
|---|---|
| EC2 instance acting up / corrupted filesystem | Yes |
| Certificate needs manual rotation | Yes |
| Auto Scaling launch config updated externally | Yes |
| Lambda function deployment package needs refresh | Yes |
| RDS instance storage type not updatable in-place | Yes |
| Kubernetes node needs replacement in node group | Yes |
| Database password rotation (create new, update refs) | Yes |
Real Example: Replacing a Corrupted EC2 Instance
# 1. Drain traffic first (if behind a load balancer)aws elbv2 deregister-targets \ --target-group-arn arn:aws:elasticloadbalancing:... \ --targets Id=i-1234567890abcdef0
# 2. Preview the replacementterraform plan -replace=aws_instance.web_server# Shows: -/+ aws_instance.web_server (tainted)# Plan: 0 to add, 0 to change, 1 to replace
# 3. Apply the replacementterraform apply -replace=aws_instance.web_server
# 4. Re-register with load balancer (or let Terraform handle via LB target group)Difference Between taint and Changing Config
| Approach | When to Use |
|---|---|
terraform apply -replace=resource | Resource is misbehaving but config is correct |
| Change config attribute that forces replace | You want to change an immutable attribute (e.g., AMI, AZ) |
terraform destroy -target=resource + apply | Same as replace but two explicit steps |
lifecycle { create_before_destroy = true } | Minimize downtime during forced replacements |
Protect Against Unintended Replacement with create_before_destroy
When a resource must be replaced, Terraform destroys the old one first by default. For load-balanced services, this causes downtime. Use create_before_destroy to avoid it:
resource "aws_instance" "web" { ami = data.aws_ami.latest.id instance_type = "t3.medium"
lifecycle { create_before_destroy = true # New instance created → traffic shifted → old instance destroyed }}terraform apply -replace=aws_instance.web# With create_before_destroy: new instance comes up first, then old is terminatedChecking Taint Status in State
# List all resources and see if any are taintedterraform state list
# Show details including taint statusterraform show# Tainted resources appear with "tainted" annotationterraform taint vs. -replace Summary
| Feature | terraform taint (classic) | -replace flag (modern) |
|---|---|---|
| Terraform version | All versions | 0.15.2+ |
| Workflow | Two commands (taint + apply) | One command |
| State file change | Yes (writes “tainted” status) | No intermediate state change |
| Visibility in plan | Yes (shows as tainted) | Yes (shows -/+) |
| Recommended | No (soft-deprecated) | Yes |
Use -replace for all new workflows. Understand taint for working with older Terraform versions or existing documentation.