Fixing the terraform state lock Error: Finding the Cause and When to Use force-unlock

4 min read

You run terraform plan or apply and get this error.

Error message
Error: Error acquiring the state lock

Error message: operation error S3: PutObject, ...
Lock Info:
  ID:        b2f1c4d8-...
  Path:      myapp-tfstate/myapp/prod/terraform.tfstate
  Who:       bob@bobs-laptop
  Created:   2026-09-17 02:14:33 UTC

Conclusion first: most of the time this error is not a failure but normal behavior, and the first response is not a command — it is waiting. The lock exists because Terraform deliberately takes it to prevent two runs from modifying the same state at the same time. The problem is the minority of cases where a lock remains when it should not, and this post is the procedure for telling the two apart.

Read the Lock Info first #

Everything you need to make the call is in the Lock Info block of the error.

  • Who: the identity that took the lock. If it is a teammate’s account, their apply is probably in progress; if it is a CI runner name, a pipeline is running.
  • Created: when the lock was taken (UTC). A few minutes ago likely means an active run; hours or days ago points to a stale lock.
  • ID: the lock identifier. You will need it for the unlock command, so copy it now.

The cause is one of three things #

  1. Someone else — or CI — is running (normal): the most common case. An apply can take tens of minutes depending on the resources (creating an RDS instance, for example), so if Created is recent, the right answer is to wait for it to finish.
  2. A run terminated abnormally and left the lock behind: a force-killed terminal, a laptop going to sleep, a CI runner canceled mid-run. The run is dead but never reached the unlock step — this is the only case that needs a manual unlock.
  3. A permissions problem makes the lock check itself fail: if you see access-denied style messages without a Lock Info block, the issue is credentials or IAM, not the lock. That is outside the scope of this post — check your access to the state bucket first.

The unlock decision procedure #

Check these in order, and unlock only if every step passes.

  1. Ask Who directly. If they say they are mid-run, wait. If it is a CI runner, check that pipeline’s run status page.
  2. Check whether Created is old enough. If the lock is older than your team’s longest apply (usually tens of minutes), it is likely stale.
  3. Once both check out, release the lock by its ID.
Unlock
terraform force-unlock b2f1c4d8-...

Use the ID from Lock Info verbatim. After unlocking, run a plan once to confirm the state is intact, and you are done.

Why force-unlock must not be used casually #

Search this error and you will find plenty of answers saying “just force-unlock it” — using it without the check procedure is dangerous. If you break the lock of an apply that is still running and start your own, two runs are now writing the same state simultaneously. That is exactly the accident the lock was there to prevent. When one side’s writes get overwritten, state and real infrastructure drift apart, and that recovery is incomparably more painful than waiting on a lock. force-unlock must keep its place as “the last resort, after confirming no one is running.”

If your locking uses DynamoDB #

For the S3 backend, besides the current standard use_lockfile = true (a .tflock object next to the state), the older standard — a DynamoDB table via the dynamodb_table argument — is still widely deployed. The behavior and the decision procedure are the same; only the physical form of a stale lock differs. In the DynamoDB approach, the lock is a LockID item in the table you configured, and in the rare cases where force-unlock fails you can inspect that item directly in the console. The differences between the two approaches and how to migrate are covered in Terraform Basics #9.

Reducing recurrence #

The usual suspects behind stale locks are humans force-killing runs and CI running concurrently. On the human side, the fix is the habit of not interrupting an apply (and if you must, at most a single Ctrl+C so Terraform gets time to clean up). On the CI side, it is a concurrency setting that serializes jobs touching the same state. The CI workflow setup is covered in Terraform Operations #1.

Summary #

  • A state lock error is usually a healthy signal. The first response is reading the Lock Info and waiting, not force-unlock
  • Ask Who, and treat the lock as stale only when Created is older than your team’s longest apply
  • The release command is terraform force-unlock <lock ID>; breaking the lock of an active run causes a concurrent state-write accident
  • The DynamoDB approach differs only in that the lock is a table item — the decision procedure is identical
  • Prevention is the habit of not interrupting apply, plus concurrency serialization in CI
X