Terraform "state lock" error: how to release safely
Shows how to release a stuck Terraform state lock safely: read the lock ID, confirm the holder is dead, then force-unlock. Use when a killed apply leaves an Error acquiring the state lock message. Not when another apply is actively running or the backend is unreachable.
TL;DR
A stuck Terraform state lock is usually a crashed or killed apply, not a real concurrent run. Check who holds the lock and whether that process is still alive; if it is dead, release with terraform force-unlock using the lock ID from the error message. Never force-unlock while another apply is actually running; two writers will corrupt the state.
Error / query
Terraform "state lock" error: how to release safelyUse this skill when
- terraform apply fails with "Error acquiring the state lock"
- A previous apply was killed (Ctrl-C, OOM, CI timeout) and the lock never released
- The lock has been held for hours and no apply is running
- You need the exact lock ID to pass to force-unlock
Not for this skill when
- Another engineer or CI job is actively applying; wait for them, do not steal the lock
- The backend is unreachable (S3/DynamoDB errors); fix connectivity first, the lock is not the problem
- State is already corrupted; that is a state repair job, not a lock release
Steps
Step 1: Read the lock ID from the error output
terraform apply 2>&1 | grep -A2 'Lock ID' | head -5Expected: a line like "Lock ID: 12345678-abcd-..."; copy it exactly, you need it for step 4.
Step 2: Check whether the lock holder is still alive
ps aux | grep -v grep | grep terraform | head -5Expected: either no terraform processes (stale lock, safe to release) or a live process (do not touch the lock).
Step 3: Confirm no CI job is currently applying
terraform plan -lock=false -detailed-exitcode >/dev/null 2>&1; echo "plan exit: $?"Expected: the plan runs fine with locking disabled; if the config itself is broken you will see it here before touching the lock.
Step 4: Release the lock with the ID from step 1
terraform force-unlock [lock-id-from-step-1]Expected: Terraform asks for confirmation, then reports the lock released; replace the bracket placeholder with the real ID.
Step 5: Verify the lock is gone and state is readable
terraform plan -lock=false 2>&1 | tail -3Expected: the plan completes with no lock error; if it shows a diff you did not expect, stop and investigate before applying.
Variant phrasings
"Error acquiring the state lock in CI"
The CI job was killed mid-apply; find the lock ID in the failed job's log, confirm no job is running, force-unlock from a workstation with the same backend config.
"DynamoDB state lock stuck"
Same fix; the lock is a DynamoDB item, force-unlock deletes it. If force-unlock cannot reach DynamoDB, check IAM permissions, not the lock.
"How long before a Terraform lock expires"
It does not expire on its own; a stale lock waits forever until someone releases it, which is why step 2 matters.
Why it happens
Terraform writes a lock record before touching state and deletes it when done; a killed process never runs the cleanup. The lock is doing its job, it just cannot tell a dead holder from a live one.
Edge cases and pitfalls
- Force-unlocking while a real apply runs can corrupt state; the ps check in step 2 is not optional.
- Some backends (consul, http) store the lock differently; the force-unlock command is the same, the lock ID format differs.
- If the state was mid-write when the process died, the lock may be the least of your problems; run plan and read the diff carefully.
- After releasing, find out why the apply died (OOM is the usual suspect) or it will happen again.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_gfVjQt1dhGViZ0ZLGHzQTQ