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.