Terraform State: Everything That Can Go Wrong (and How to Fix It)
Locked states, drift, corrupted backends, team collisions — battle-tested recovery playbooks for every state disaster.

Neeraj Kumar
Cloud & DevOps Engineer
Terraform's state file is both its greatest strength and its most fragile point. After years of writing modular Terraform for VPCs, ECR, IAM and EKS, here are the failure modes I've hit — and the exact recovery steps for each.
1. The stuck lock
Someone's apply got killed mid-run and now every plan fails with "Error acquiring the state lock". First, confirm nobody is actually running Terraform. Then release the lock:
terraform force-unlock <LOCK_ID>
# The lock ID is printed in the error message itselfNever delete the lock entry from DynamoDB manually unless force-unlock itself fails — the CLI path validates you are unlocking the right state.
2. Drift: reality no longer matches state
Someone changed a security group in the console. Now Terraform wants to "fix" it back. You have three choices: accept reality into code (update the .tf to match, then apply), revert reality (just apply), or refresh your view first:
terraform plan -refresh-only # see what changed outside Terraform
terraform apply -refresh-only # accept those changes into state3. Importing resources created outside Terraform
terraform import aws_s3_bucket.logs my-existing-bucket
# Terraform 1.5+: use an import block instead
import {
to = aws_s3_bucket.logs
id = "my-existing-bucket"
}4. Moving resources without destroying them
Refactoring modules is the classic trap — rename a resource and Terraform plans destroy-and-recreate. Use moved blocks (or terraform state mv for older versions):
moved {
from = aws_instance.app
to = module.compute.aws_instance.app
}5. The corrupted or lost state file
- S3 backend with versioning enabled: restore the previous object version. This is why versioning on the state bucket is non-negotiable.
- No backup: rebuild state with import blocks, resource by resource. Painful but doable.
- Prevention: enable S3 versioning, use DynamoDB locking, and never store state in git.
The rules that prevent 90% of this
- Remote state (S3 + DynamoDB lock) from the very first commit.
- One state per environment — small blast radius beats convenience.
- All changes through CI, never from laptops.
- Turn on state bucket versioning today. Right now. Seriously.