← Back to all articles
TerraformApril 2, 2026📖 12 min read

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

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:

bash
terraform force-unlock <LOCK_ID>
# The lock ID is printed in the error message itself

Never 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:

bash
terraform plan -refresh-only   # see what changed outside Terraform
terraform apply -refresh-only  # accept those changes into state

3. Importing resources created outside Terraform

bash
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):

hcl
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.
CloudCodeAI — Empower Engineering

Enjoyed this article?

I share AWS & DevOps tutorials on my YouTube channel CloudCodeAI and train engineers hands-on.