Terraform Error: 'removed' block still declared in configuration
Fix Terraform's 'Removed resource still present' error: a removed block must target a resource whose resource/module declaration has actually been deleted from your config.
- #terraform
- #iac
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Exact Error Message
╷
│ Error: Removed resource still present
│
│ on removed.tf line 1:
│ 1: removed {
│
│ This statement declares a removal of the resource aws_s3_bucket.legacy,
│ but this resource is still declared in the configuration.
│
│ Remove the resource block for aws_s3_bucket.legacy from the configuration
│ before removing it from the state with a removed block.
╵
You may also see the variant Cannot remove module.<name> because it is still declared when the removed block targets a whole module call that has not been deleted.
What It Means
The removed block (introduced in Terraform 1.7) tells Terraform to drop a resource or module from state without destroying the real infrastructure. It is the declarative replacement for terraform state rm.
For that to be valid, the corresponding resource or module block must no longer exist in your configuration. Terraform refuses to process a removed block while the same address is still declared, because that would be contradictory: you are simultaneously asking Terraform to manage the resource and to forget it. The fix is always to delete the actual declaration, leaving only the removed block behind.
Common Causes
- You added a
removedblock but forgot to delete the matchingresource "aws_s3_bucket" "legacy" { ... }declaration. - The declaration was commented out rather than deleted — Terraform still parses
removedcorrectly but a stray duplicate resource block remains elsewhere. - A module still instantiates the resource internally, while you added the
removedblock at the root. - You targeted the wrong address (typo in resource type or name) so it collides with a live declaration.
- A
for_each/countinstance was removed individually but the parent resource block is still present.
Diagnostic Commands
Confirm which addresses currently exist in configuration versus state:
terraform state list | grep legacy
Search the codebase for the still-present declaration:
grep -rn 'aws_s3_bucket" "legacy"' *.tf modules/
Run a validate to see the parser’s view of both blocks:
terraform validate
Run a plan to reproduce the exact error and confirm the address in the message:
terraform plan
Step-by-Step Resolution
-
Read the error carefully and note the exact address it names (here,
aws_s3_bucket.legacy). -
Find and delete the resource declaration for that address. Do not comment it out — remove the whole block:
# DELETE this entire block
resource "aws_s3_bucket" "legacy" {
bucket = "my-legacy-bucket"
}
- Keep only the
removedblock, and make surelifecycle.destroy = falseso the real bucket is preserved:
removed {
from = aws_s3_bucket.legacy
lifecycle {
destroy = false
}
}
- If the resource lives inside a module, delete it from the module too, or move the
removedblock into that module and target the correct nested address:
removed {
from = module.storage.aws_s3_bucket.legacy
lifecycle {
destroy = false
}
}
- Re-run the plan. Terraform should now report that the resource will be removed from state only:
terraform plan
Plan: 0 to add, 0 to change, 0 to destroy, 1 to forget.
- Apply, then delete the now-unused
removedblock in a later commit once the state has been reconciled:
terraform apply
Prevention
- Treat
removedas a two-part change: delete the declaration and add the block in the same commit. - Always include
lifecycle { destroy = false }when you want to keep the real infrastructure — omitting it destroys the resource on apply. - Use
grepor your editor’s project search to confirm no other file re-declares the address before applying. - Remove
removedblocks once they have served their purpose; leaving them indefinitely clutters the config and can mask future re-additions. - When refactoring modules, prefer
movedblocks for renames and reserveremovedfor genuine ownership handoffs. If you are unsure which refactor block fits, the Terraform refactoring prompts can generate a checklist for your exact change.
Related Errors
Moved object still exists— the analogous problem formovedblocks when both endpoints are declared.Resource already managed by Terraform— animportblock colliding with an existing declaration.Reference to undeclared resource— you deleted a declaration that other expressions still reference.Cannot remove module ... still declared— the module-call variant of this same error.
Frequently Asked Questions
Does a removed block destroy my real infrastructure? Only if you omit lifecycle { destroy = false }. With destroy = false, Terraform forgets the resource but leaves the actual cloud object untouched.
Why not just run terraform state rm? removed blocks are declarative and reviewable in version control, so the intent is captured in your codebase and applied consistently across a team, unlike an imperative one-off command.
Can I keep the resource block commented out instead of deleting it? Yes — commented HCL is ignored by the parser, so a fully commented-out declaration satisfies the requirement. Deleting it is cleaner and avoids confusion.
Do I have to remove the removed block afterwards? Not immediately, but it is good hygiene to delete it in a follow-up commit once the state is reconciled, since it no longer does anything. For more refactoring walkthroughs, see the Terraform guides.
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.