Fix "Error Acquiring the State Lock" in Terraform
A stuck state lock stops every apply cold, and the fix is one command away once you confirm nobody else is actually running Terraform.
Key takeaways
- Terraform's "Error acquiring the state lock" means another operation holds the state lock, or a crashed or killed run left a stale one.
- Read the Who and Created fields in Lock Info, confirm no apply is live, then run terraform force-unlock with the exact lock ID.
- Prevent repeats with CI concurrency groups and -lock-timeout.
On this page
"Error acquiring the state lock" means another Terraform operation owns the state lock, or a run that crashed left a stale one behind. Read the Who and Created fields in the error, confirm nothing is running, then release it with terraform force-unlock <LOCK_ID>. Nothing in your infrastructure is broken; Terraform is refusing to proceed on purpose.
Verified against
Terraform 1.17 (1.17.1, released 2026-10-28), against the developer.hashicorp.com state locking, force-unlock and S3 backend pages. Checked October 2026.
What state locking is and why it exists
Terraform stores the mapping between your configuration and real resources in a state file. Any operation that could write state (apply, destroy, import, state mv) first takes a lock on the backend so only one writer runs at a time. The docs are blunt: if state locking fails, Terraform does not continue.
Without locking, two concurrent applies can interleave their writes and leave state describing resources that do not match reality, one of the hardest failure modes to recover from. The lock is taken at the start of the operation and released when it finishes cleanly. Most of the time you see this error, a previous run died before the release.
What the error looks like
The message hands you everything you need. This one is from the S3 backend with a DynamoDB lock table:
$ terraform apply
Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│
│ Error message: operation error DynamoDB: PutItem, ...
│ ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│ ID: d8f3c1a2-9b7e-4f6a-8c21-2a5f0e9d7b41
│ Path: my-tf-state/prod/terraform.tfstate
│ Operation: OperationTypeApply
│ Who: kiril@build-agent-07
│ Version: 1.17.1
│ Created: 2026-10-29 14:02:11.884213 +0000 UTC
│ Info:
╵
Read the Lock Info block first. Who is the user and machine holding the lock, Created is its age, Operation is what it was doing. If Who is your own CI runner from twenty minutes ago and no job is active, the lock is stale. If Who is a colleague and the timestamp is thirty seconds old, someone is applying right now and you should wait.
The three causes
A previous run crashed or was interrupted. Someone hit Ctrl-C mid-apply, a laptop slept, or a CI job hit its timeout and got killed. Terraform never reached the release step.
A genuine concurrent run. Two pipelines fired off the same branch, or a teammate is applying by hand while CI does the same. This is what the lock exists to catch. The fix is patience, not force.
A network blip to the backend. The lock was written, then the connection dropped before the release completed. The lock record persists though nothing is running.
The safe fix
The order of operations matters more than the command.
- Check whether anyone is running Terraform: your CI dashboard for in-flight jobs, your team channel, and the
WhoandCreatedfields against reality. - Only once you are confident nothing is live, release the lock with the exact ID from the error:
$ terraform force-unlock d8f3c1a2-9b7e-4f6a-8c21-2a5f0e9d7b41
Do you really want to force-unlock?
...
Enter a value: yes
Terraform state has been successfully unlocked!
Run it from the same configuration directory, because the command removes the lock on the state for the current configuration. -force skips the prompt. Per the docs, force-unlock does not modify your infrastructure, and the lock ID acts as a nonce so you cannot release the wrong lock by accident.
The danger of force-unlock
HashiCorp's locking page says to "be very careful with this command" and to use it only "to unlock your own lock in the situation where automatic unlocking failed." Unlock a lock that a live apply owns and you have invited a second writer into the state. Recovering can mean hand-editing JSON, re-importing resources, or restoring a backend object version, all under pressure. Never wire force-unlock into an automatic retry or an unattended pipeline step.
The error strings by backend, and the last-resort cleanup
| Backend | What the lock error says | Where the lock lives |
|---|---|---|
S3 + DynamoDB (dynamodb_table) | ConditionalCheckFailedException: The conditional request failed | An item in the table whose LockID is the state Path |
S3 native (use_lockfile = true) | An S3 PreconditionFailed error on the lock object | A <key>.tflock object next to the state |
| GCS | An Error 412 precondition failure on a .tflock object | A lock object in the state bucket |
| azurerm | state blob is already locked | A lease on the state blob |
| Local | Local state cannot be unlocked by another process | The local process; stop it instead |
When force-unlock itself cannot reach the backend, go to the store directly, and only after confirming no run is active. For DynamoDB, the lock item's LockID is the Path from Lock Info. Do not touch the separate ...-md5 item; that holds the state checksum, not the lock:
$ aws dynamodb get-item --table-name terraform-locks \
--key '{"LockID": {"S": "my-tf-state/prod/terraform.tfstate"}}'
$ aws dynamodb delete-item --table-name terraform-locks \
--key '{"LockID": {"S": "my-tf-state/prod/terraform.tfstate"}}'
For S3-native locking, the lock is an ordinary object:
$ aws s3 ls s3://my-tf-state/prod/
$ aws s3 rm s3://my-tf-state/prod/terraform.tfstate.tflock
On HCP Terraform, locking is per workspace and there is no table to edit. Unlock from the workspace UI or with force-unlock, and check who locked it first: humans can lock workspaces deliberately, and it looks the same.
Prevention: move off DynamoDB, serialize CI, wait instead of failing
As of October 2026 the S3 backend docs mark DynamoDB-based locking as deprecated and say it "will be removed in a future minor version." Turn on S3-native locking; both arguments can be set together while you migrate:
# backend.tf
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
use_lockfile = true
}
}
Then run terraform init -reconfigure. The role needs s3:GetObject, s3:PutObject and s3:DeleteObject on the .tflock key.
Serialize CI so only one job per state runs at a time. In GitHub Actions, a concurrency group queues runs instead of colliding (GitLab's equivalent is resource_group):
# .github/workflows/terraform.yml
concurrency:
group: terraform-prod
cancel-in-progress: false
Keep cancel-in-progress false: cancelling a running apply is exactly how locks get orphaned. Finally, let Terraform wait out short overlaps instead of failing instantly:
$ terraform apply -lock-timeout=120s
For the broader catalog of plan, provider, and module failures, keep the Terraform error troubleshooting guide handy. If you are restructuring backends anyway, migrating Terraform state covers the move.
Where this breaks
Everything here assumes a backend that supports locking; not all do, and each backend's page says whether it does. If yours does not, the lock error never appears and concurrent runs simply corrupt state, so serializing CI is your only protection. And force-unlock cannot fix a state that two writers already mangled; that is a restore from a versioned backup, not an unlock.
Questions people ask
What does "Acquiring state lock. This may take a few moments" mean?
It is the normal message Terraform prints before any command that can write state, and it usually clears in a second. If it hangs and then fails with "Error acquiring the state lock", another process holds the lock or a dead run left it behind. With -lock-timeout=120s set, Terraform keeps retrying for that long before giving up instead of failing immediately.
Is it safe to run terraform force-unlock?
Only after you confirm no operation is live. HashiCorp's locking docs say force unlock "should only be used to unlock your own lock in the situation where automatic unlocking failed." Unlocking someone else's active run lets two writers touch the same state, which is exactly the corruption locking prevents. The command needs the exact lock ID, and it never changes real infrastructure.
Can I just use -lock=false?
You can for most commands, but HashiCorp's docs say they do not recommend it, and it skips the safety check rather than clearing the stale lock. It can be tolerable for a read-only plan on a state nobody else is touching. For apply, release the stale lock properly with terraform force-unlock and the ID from the error.
The call we'd make
Treat a lock error as a question, not an emergency. Read the Lock Info, confirm from CI and your team that nothing is running, then force-unlock with the exact ID. Reach for backend-level deletion only when force-unlock cannot talk to the backend. Then spend ten minutes switching to use_lockfile, adding a concurrency group, and setting -lock-timeout, so the same interruption does not page you next week.
Sources
Cite this article
Kiril Urbonas (2026). Fix "Error Acquiring the State Lock" in Terraform. DevOpsNess. Updated 2026-10-31. https://www.devopsness.com/blog/fix-terraform-state-lock
[Fix "Error Acquiring the State Lock" in Terraform](https://www.devopsness.com/blog/fix-terraform-state-lock) (Kiril Urbonas, DevOpsNess, updated 2026-10-31)
More from DevOps
Explore more articles in this category
15 SRE Interview Questions With Answers, From SLOs to On-Call
SRE interviews test numbers and judgment. Fifteen questions on SLIs, error budgets, incident command, toil, capacity, on-call and postmortems, plus a systems-design scenario, each with the answer that scores and the one that loses the room.
Sign Container Images With Cosign: Keyless GitHub Actions Workflow, SBOM and In-Cluster Checks
One workflow that builds, signs keylessly with GitHub OIDC, attaches an SBOM and SLSA provenance, plus the Kyverno policy that refuses anything unsigned. Copy it, change three names, ship it.
Fix Prometheus High Cardinality: 6 Commands to Find and Drop Bad Series
When Prometheus runs out of memory, one label is almost always to blame. Six commands to find it, the relabel rules that drop it, and the scrape limits that stop it coming back.
You might have missed
Evergreen posts worth revisiting.