Skip to content
daniel@cosenza:~/blog
SRE & DevOpsFix October 20, 2025 3 min read

Recovering from a Stuck Terraform State Lock

terraform plan or apply hangs, then fails with 'Error acquiring the state lock.' Here's how to confirm it's genuinely stale before force-unlocking it.

terraform plan or terraform apply fails with an error like Error acquiring the state lock — Terraform’s remote state locking mechanism (backed by DynamoDB, a Terraform Cloud workspace lock, or similar, depending on your backend) believes another operation already holds the lock.

Step 1: don’t force-unlock immediately — check if it’s genuinely stale first

The error output itself includes useful context:

Error: Error acquiring the state lock

Lock Info:
  ID:        7c1b3f2e-...
  Path:      prod/terraform.tfstate
  Operation: OperationTypeApply
  Who:       ci-runner-42@github-actions
  Created:   2025-10-20 14:02:33 UTC

Check whether the process or CI run identified in Who and Created is actually still running. If a CI pipeline named in the lock is genuinely still executing (check your CI system’s dashboard directly), the lock is legitimate — waiting for it to finish is the correct action, not force-unlocking underneath it.

Step 2: confirm the holding process is actually dead

If the Created timestamp is old relative to how long that kind of operation normally takes, and the named CI run/process shows as finished, crashed, or cancelled in your CI system — not currently executing — the lock is stale, left behind by a process that didn’t clean up after itself (a killed pipeline, a network partition during apply, a manually interrupted terraform apply).

Step 3: force-unlock, once you’re confident it’s stale

terraform force-unlock 7c1b3f2e-...

This requires the lock ID shown in the error output — Terraform deliberately requires this explicit ID rather than a blanket “unlock everything” command, specifically to make you look at and confirm the correct lock before removing it.

Step 4: after unlocking, check for actual state corruption

A lock left behind by a crashed or killed apply sometimes means that operation was interrupted mid-write to the state file itself, not just holding the lock idly. After force-unlocking, run a plan and inspect it carefully before applying anything:

terraform plan

If the plan shows unexpected, large-scale changes (resources it thinks need recreating that you didn’t touch), this can indicate the interrupted operation left state in a partially-updated condition — compare against your remote backend’s state file versioning (S3 versioning, Terraform Cloud’s state history) to check whether a slightly earlier state version is actually more accurate.

Step 5: recovering from a truly corrupted state file

If the state is genuinely inconsistent with reality, terraform state subcommands let you inspect and repair specific resources without needing to restore an entire backup:

terraform state list
terraform state show aws_instance.web
terraform refresh

terraform refresh (or terraform plan -refresh-only in newer versions) re-syncs state against real infrastructure without proposing configuration changes — often enough to resolve a state file that drifted or was left slightly stale by an interrupted operation, without needing a full restore from backup.

Preventing this going forward

The most effective prevention is making sure CI pipelines running terraform apply have reasonable timeouts and handle cancellation gracefully (running with a lock-release step even on failure/cancellation), and that remote state backends have versioning enabled (S3 bucket versioning, in particular) so a bad state write is always recoverable by reverting to a prior version — rather than relying on locks alone to prevent every possible interruption scenario.

Why locking exists in the first place

State locking exists specifically to prevent two concurrent apply operations from writing to the same state file at once, which would corrupt Terraform’s record of what it actually manages. A stuck lock is locking doing exactly what it’s supposed to do — flag a potential conflict — the work here is confirming whether that flagged conflict is real (another operation genuinely in progress) or stale (a crashed process that never released it), not treating every lock error as something to immediately force through.