Terraform: Error acquiring the state lock (tested)

Error: Error acquiring the state lock means another Terraform run already holds the lock on your state file, so this run refuses to touch it and risk corrupting it. We reproduced the error for real with two genuinely concurrent runs on Terraform v1.16.5, and found something most generic advice gets wrong for the local backend: force-unlock is often neither the right fix nor even accepted.

1. The exact error, from two real concurrent runs

We ran terraform apply in the background against a resource with a 10-second provisioner, then immediately ran terraform plan in the same working directory:

Error: Error acquiring the state lock

Error message: resource temporarily unavailable
Lock Info:
  ID:        96997dec-6398-dc52-2181-cb385000739c
  Path:      terraform.tfstate
  Operation: OperationTypeApply
  Who:       jacque@jacque-homelab
  Version:   1.16.5
  Created:   2026-10-03 13:50:17 UTC

This is Terraform working correctly, not a bug: the Who/Operation/Created fields tell you exactly which run holds it. Once the first run finished (10s later), a retried plan succeeded immediately with no error — the lock had already been released normally.

2. The polite fix: -lock-timeout waits, it doesn't skip the lock

We re-ran the same scenario with terraform plan -lock-timeout=15s. It did not fail instantly — it waited the full window (measured: real 0m15.035s), retrying internally, then still failed with the identical error because the holding apply hadn't finished yet. -lock-timeout is a patience setting, not a guarantee: set it longer than your typical run takes (CI pipelines commonly use 5-10 minutes) rather than assuming any value "fixes" the error.

3. A real stale lock: killing the process, then testing force-unlock

Generic advice says: if a run crashes and leaves the state locked, run terraform force-unlock <LOCK_ID>. We tested this for real by starting an apply, then sending it kill -9 mid-run (simulating a killed CI job or a crashed laptop) — the realistic cause of a "stuck" lock. A following plan still showed the lock as held, confirming it was genuinely stuck. We then ran:

terraform force-unlock 1fd358e5-ce0c-5959-8ec2-5c0eb0fb1345
Local state cannot be unlocked by another process

force-unlock itself errored. But the very next terraform plan we ran, with no other action taken, succeeded immediately with no lock error at all. The lock had already released itself.

Why: the local backend's lock is an OS-level advisory file lock (flock), tied to the holding process's lifetime. When the process dies — crash, OOM kill, kill -9, even a laptop losing power — the operating system itself releases the lock immediately as part of process cleanup, before Terraform's own force-unlock command ever gets a chance to matter. force-unlock is explicitly rejected for the local backend precisely because the backend doesn't track locks as data Terraform can override — the OS already owns that job.

4. Remote backends are genuinely different (per HashiCorp's docs, not measured on this box)

This local-backend behavior does not generalize. Backends like S3 (with a DynamoDB lock table), Consul, or Terraform Cloud store the lock as a separate record that a crashed process cannot clean up on its own — there is no OS process to tie the lock's lifetime to. For those backends, a genuinely crashed run really can leave a permanently stuck lock, and terraform force-unlock <LOCK_ID> is the real, necessary fix (per HashiCorp's own docs; we did not reproduce this ourselves since it needs real cloud infrastructure). Before running it anywhere, confirm via your team/CI dashboard that no run is actually still in progress — force-unlocking a lock that's still legitimately held risks exactly the state corruption the lock exists to prevent.

5. How to tell which situation you're in

SymptomLikely real stateRight move
Error appears, another run is actively running (CI dashboard, a teammate says so)Lock is legitimately heldWait, or set -lock-timeout long enough to wait it out
Local backend, the holding process is confirmed deadOS already released the lockJust retry the command — we measured it works immediately, no force-unlock needed or accepted
Remote backend (S3/DynamoDB, Consul, TFC), holding run confirmed dead/cancelledLock record can genuinely be stuckterraform force-unlock <LOCK_ID> (per docs) after confirming no run is actually in progress

Frequently asked questions

Does terraform force-unlock fix 'Error acquiring the state lock'?

It depends on your backend. We measured that for the default local backend, force-unlock actively refuses to run ('Local state cannot be unlocked by another process') — and it isn't needed anyway, because the OS releases the underlying file lock automatically the moment the holding process dies. For remote backends (S3+DynamoDB, Consul, Terraform Cloud), the lock is a separate record that can genuinely get stuck, and force-unlock is the documented real fix there.

Why did my stuck-looking lock clear itself without me doing anything?

We reproduced this exact scenario: killing a terraform process mid-apply with kill -9 (simulating a crashed CI job), then testing immediately after. The lock was still showing as held right after the kill, but the very next command succeeded with zero errors — the local backend's lock is tied to the OS process via flock, which releases automatically on process death, often faster than you'd expect to check.

What does -lock-timeout actually do?

It controls how long a command will wait and retry before giving up, not whether it ignores the lock. We measured terraform plan -lock-timeout=15s actually blocking for the full 15.035 seconds before still failing, because the holding process hadn't released the lock yet in that window. Set it comfortably longer than your typical apply/plan duration.

Is it safe to just add -lock=false to make this error go away?

Only if you're certain no concurrent run exists — -lock=false disables the exact safety check that's protecting you from two processes corrupting the same state file simultaneously. We didn't need it in any of our reproductions; waiting out a real lock, or confirming a crashed process already released it, are both safer.

Does this local-backend auto-release behavior apply to Terraform Cloud or remote state too?

No — per HashiCorp's own documentation (not something we could measure without real cloud infrastructure), remote backends like S3+DynamoDB, Consul, and Terraform Cloud store the lock as an independent record with no OS process tied to its lifetime, so a crashed run there really can leave a permanently stuck lock. That's the one case where terraform force-unlock is the genuine, necessary fix — confirm via your CI/team first that nothing is actually still running before using it.

Written with AI assistance. Measured on 2026-10-03 with Terraform v1.16.5 on Linux, using the default local backend (no cloud credentials involved) with a terraform_data resource and a local-exec provisioner to hold the lock for a controlled window. Every message, timing and exit path below is copy-pasted output, not paraphrased. PlainHeaders is our extension; Terraform is free.