Terraform State Explained: Locking, Backups, and Recovery

terraform state diagram showing state file, backup file, and s3 native locking

Terraform state causes more real incidents than almost anything else in the tool — a stuck lock, a corrupted file, a state rm that touched the wrong resource. Most of the anxiety is unnecessary once you know two things most tutorials skip: Terraform backs up state automatically before every change, and it can’t be turned off. Here’s terraform state explained with the commands that actually matter, verified against HashiCorp’s own docs.

Terraform State Backups: The One You Didn’t Know You Had

Per Terraform’s state documentation, Terraform “stores each workspace’s state in a local file named terraform.tfstate, and a backup of the previous state in terraform.tfstate.backup.” That backup isn’t optional. Per the state command reference, every command that modifies state “write[s] backup files,” and backups for state modification “can not be disabled” — HashiCorp’s own words, because of how sensitive state content is.

Practically: before you panic about a bad terraform state rm or state mv, check for terraform.tfstate.backup first. It’s sitting right next to your state file, one command edit behind.

Terraform State Commands That Actually Matter

Six subcommands live under terraform state, and you’ll use maybe three of them regularly:

Command What it does
terraform state list Lists every resource currently tracked in state
terraform state show <resource> Shows the full recorded attributes for one resource
terraform state mv <src> <dst> Moves a resource to a new address in state, without touching real infrastructure
terraform state rm <resource> Removes a resource from state tracking, without destroying it
terraform state pull Retrieves and prints the raw remote state
terraform state replace-provider Updates which provider a resource is associated with

mv and rm are the two people reach for under pressure — renaming a resource in code without triggering a destroy/recreate, or un-tracking something Terraform shouldn’t manage anymore. Both are state-only operations: nothing in your real infrastructure changes. That’s also exactly why the automatic backup exists — a bad mv or rm is a state-file mistake, and the backup is how you undo it.

State Locking: The DynamoDB Pattern Is Deprecated

If you learned Terraform’s S3 backend from an older tutorial, it told you to provision a DynamoDB table for state locking. That guidance is now out of date. Per the S3 backend docs, DynamoDB-based locking is explicitly deprecated: “Locking can be enabled via S3 or DynamoDB. However, DynamoDB-based locking is deprecated and will be removed in a future minor version.”

The current approach is native S3 locking, enabled with one config key:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key = "prod/terraform.tfstate"
    region = "us-east-1"
    use_lockfile = true # native S3 locking — no DynamoDB table needed
  }
}

The lock lives alongside your state as a file with a .tflock suffix. If you’re still running a DynamoDB table purely for Terraform locking, it’s no longer required — use_lockfile = true replaces the dynamodb_table setting, and one less piece of infrastructure to maintain and pay for.

When a Lock Won’t Release: force-unlock

A crashed apply, a killed CI job, or a network drop mid-run can leave a lock in place with nothing actually running. Per the force-unlock reference, the command is:

terraform force-unlock [options] LOCK_ID

The important line in HashiCorp’s own docs: “This command does not modify your infrastructure.” It only removes the lock record — it doesn’t touch state content or real resources, which is why it’s safe to run once you’ve confirmed nothing else is actually mid-apply. One real caveat worth knowing before you reach for it: “Local state files cannot be unlocked by another process” — force-unlock is a remote-backend feature. If you’re on purely local state, a stuck lock means finding and clearing the lock file yourself, not running this command.

A Practical Recovery Workflow

Put together, here’s the actual sequence for the two most common state incidents:

“I ran terraform state rm on the wrong resource.”
1. Don’t run terraform apply yet — that’s what would actually recreate or destroy something based on the now-wrong state.
2. Check terraform.tfstate.backup — it has the pre-rm version.
3. Restore the specific resource block from the backup, or use terraform state push with a corrected copy if you’re on a remote backend.

“A lock won’t release and nothing is actually running.”
1. Confirm no CI job or teammate has an apply genuinely in progress — check your CI dashboard, not just your own terminal.
2. Get the lock ID from the error message Terraform printed when the lock conflict occurred.
3. Run terraform force-unlock LOCK_ID. Remember this doesn’t work for purely local state.

Neither of these requires touching real infrastructure. That’s the point of how Terraform separates state operations from apply operations — it’s worth knowing before the 2am incident, not during it.

For the tool that automates a lot of this multi-environment state and backend setup, our Terragrunt vs Terraform guide covers remote_state blocks that auto-generate backend config like the one above. If you’re deciding between engines entirely, our OpenTofu vs Terraform guide and our look at what’s new in OpenTofu 1.13 cover the state-encryption feature Terraform doesn’t have natively.

Frequently Asked Questions

Q: Does Terraform back up state automatically?
A: Yes. Per HashiCorp’s own docs, every state-modifying command writes a backup to terraform.tfstate.backup, and this cannot be disabled.

Q: Do I still need a DynamoDB table for Terraform state locking?
A: No, not for new setups. DynamoDB-based S3 locking is deprecated. Use use_lockfile = true in your S3 backend config for native locking instead.

Q: Does terraform force-unlock destroy anything?
A: No. Per HashiCorp’s docs, it explicitly does not modify your infrastructure — it only removes the lock record. It also doesn’t work on local (non-remote) state files.

Q: What’s the difference between terraform state rm and terraform destroy?
A: state rm only removes a resource from Terraform’s tracking — the real infrastructure is untouched. destroy actually deletes the real resource. They’re easy to confuse in a panic; state rm is the safe one.

Q: Where is Terraform state stored by default?
A: Locally in terraform.tfstate unless a remote backend (S3, HCP Terraform, etc.) is configured. HashiCorp recommends a remote backend for anything beyond solo experimentation.

Quick Summary:
– Terraform state modification commands (state mv, state rm) always write an undeletable backup to terraform.tfstate.backup first
– DynamoDB-based S3 state locking is deprecated — use use_lockfile = true for native S3 locking instead
– terraform force-unlock LOCK_ID removes a stuck lock without touching infrastructure, but doesn’t work on local state
– The safe recovery path for a bad state edit is the .tfstate.backup file, not terraform apply
– state rm and terraform destroy are not the same thing — only one of them touches real infrastructure

Check terraform state list before any bulk state operation — confirming what’s actually tracked takes ten seconds and prevents the kind of terraform state mistake that needs a recovery workflow in the first place.

Related guides

Leave a Reply