Fix Terraform 'Error acquiring the state lock'

Quick answer
Terraform failed with 'Error acquiring the state lock' and printed a Lock Info block. Here's how to read that block, decide whether to wait or force-unlock, and fix the backend so it stops happening.
- What this error means
- Step 1: See the actual error
- Cause 1: A genuinely concurrent run holds the lock
- Cause 2: Stale lock from a crashed or killed run
- Cause 3 (S3 backend): DynamoDB lock table missing or misconfigured
8 min read · DevOps & Platform
Fix Terraform 'Error acquiring the state lock'
You run terraform apply and it stops before doing anything:
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: 3f9c2a1b-8e4d-4f77-9c2a-1b8e4d4f7790
Path: my-app/terraform.tfstate
Operation: OperationTypeApply
Who: ci-runner@build-42
Version: 1.9.5
Created: 2026-07-02 10:14:03.221 +0000 UTC
Info:
This is Terraform (and OpenTofu — same behavior) telling you the state file is locked, so it won't touch it. State locking is what stops two runs from writing the same state at once and corrupting it. The message is a safety feature, not a bug. The fix depends entirely on why the lock is held.
What this error means
Remote backends that support locking — S3 with a DynamoDB table, azurerm blob storage, gcs — take a lock before any read/write operation and release it when the run finishes. If a lock already exists when a new run starts, you get this error.
The Lock Info block is the whole story. Read it before doing anything:
- ID — the lock identifier you'll pass to
force-unlockif the lock is stale. - Who — the user and host that created the lock. If it's a CI runner or a colleague, someone else may be mid-run.
- Operation — what the lock holder is doing (
OperationTypeApply,OperationTypePlan). - Created — when the lock was taken. A timestamp from 3 seconds ago means a live run; one from 3 hours ago means a crash.
Never blindly force-unlock. That's how state gets corrupted.
Step 1: See the actual error
Confirm whether a run is genuinely in progress before touching the lock.
1# Re-read the full Lock Info — note the ID, Who, and Created time
2terraform plan
3
4# Is a colleague or CI job still running? Check your CI system:
5gh run list --workflow terraform.yml --limit 5
6
7# Inspect the lock record directly in DynamoDB (S3 backend)
8aws dynamodb get-item \
9 --table-name terraform-locks \
10 --key '{"LockID":{"S":"my-tf-state-bucket/my-app/terraform.tfstate"}}'If Created is recent and Who maps to a job that's still running, stop here — go to Cause 1. If the run that took the lock is definitely dead, go to Cause 2.
Cause 1: A genuinely concurrent run holds the lock
The most common and most benign case. Another apply — a teammate on their laptop, or a CI pipeline — is actively running against the same state. The lock is doing exactly its job.
What you'll see: Created is seconds or a couple of minutes old, and Who corresponds to a job you can confirm is still alive.
Fix: Wait. Do not force-unlock a live run — you'll get two processes writing state simultaneously and corrupt it. When the other run finishes, the lock releases and your command proceeds. If you want Terraform to wait rather than fail immediately, use:
terraform apply -lock-timeout=120sThat retries lock acquisition for two minutes before giving up — useful when short CI jobs briefly overlap.
Cause 2: Stale lock from a crashed or killed run
If a previous run was Ctrl-C'd, the CI runner was terminated, the network dropped, or the machine died mid-apply, Terraform never got to release the lock. The lock record is orphaned and will block every future run until you clear it.
What you'll see: Created is old (minutes to hours), and the Who/job it names is definitely no longer running.
Fix: Force-unlock using the exact ID from the Lock Info block.
# Confirm no active run first, then release the specific lock
terraform force-unlock 3f9c2a1b-8e4d-4f77-9c2a-1b8e4d4f7790OpenTofu users run the identical command with tofu:
tofu force-unlock 3f9c2a1b-8e4d-4f77-9c2a-1b8e4d4f7790Terraform will ask for confirmation. Only do this once you are certain no apply is in flight — force-unlocking a running apply is the single fastest way to corrupt your state. Always pass the specific lock ID; there is no "unlock everything" shortcut on purpose.
Cause 3 (S3 backend): DynamoDB lock table missing or misconfigured
With the S3 backend, the lock lives in a DynamoDB table. If the table name is wrong, the table doesn't exist, or its primary key isn't a hash key named exactly LockID, locking breaks and you can see acquisition failures or ResourceNotFoundException.
Fix: Make sure the backend points at a real table and the table has the correct schema.
1terraform {
2 backend "s3" {
3 bucket = "my-tf-state-bucket"
4 key = "my-app/terraform.tfstate"
5 region = "us-east-1"
6 dynamodb_table = "terraform-locks"
7 encrypt = true
8 }
9}The table must use LockID as the partition key — Terraform looks for that exact attribute name:
1resource "aws_dynamodb_table" "terraform_locks" {
2 name = "terraform-locks"
3 billing_mode = "PAY_PER_REQUEST"
4 hash_key = "LockID"
5
6 attribute {
7 name = "LockID"
8 type = "S"
9 }
10}Note: newer Terraform versions support S3-native locking via use_lockfile = true, removing the DynamoDB requirement — but if you configured dynamodb_table, the table must be correct.
Stuck on this in production?
We debug exactly this kind of issue for platform teams — usually in a single working session.
Cause 4: Missing IAM or backend permissions
Terraform can also fail to lock because the credentials can't write the lock item. On S3 backends that's usually missing DynamoDB permissions; on azurerm/gcs it's missing blob/object write access. The symptom is an AccessDenied wrapped in the lock error.
Fix: Grant read/write on the lock table (and the state bucket) to the identity running Terraform.
1data "aws_iam_policy_document" "tf_state" {
2 statement {
3 actions = ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"]
4 resources = [aws_dynamodb_table.terraform_locks.arn]
5 }
6
7 statement {
8 actions = ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"]
9 resources = ["arn:aws:s3:::my-tf-state-bucket/my-app/*"]
10 }
11
12 statement {
13 actions = ["s3:ListBucket"]
14 resources = ["arn:aws:s3:::my-tf-state-bucket"]
15 }
16}Without PutItem/DeleteItem, Terraform can never acquire or release the lock cleanly.
Cause 5: CI pipelines running concurrently
If two pipeline runs on the same branch — or a push and a scheduled run — hit terraform apply against the same state at once, they race for the lock and one fails. This is Cause 1's automated cousin, and the fix is to make it structurally impossible.
Fix: Serialize Terraform jobs with a concurrency group so a second run queues instead of racing.
# GitHub Actions
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: falsecancel-in-progress: false matters here — cancelling a running apply mid-flight is what creates the stale locks from Cause 2. Queue, don't cancel.
Quick reference
1# 1. Read the Lock Info block — ID, Who, Created
2# 2. Is the run still alive? Wait (or -lock-timeout).
3terraform apply -lock-timeout=120s
4
5# 3. Confirmed dead run? Release the exact lock:
6terraform force-unlock <LOCK_ID>
7tofu force-unlock <LOCK_ID> # OpenTofu| Clue in Lock Info | Likely cause | Action |
|---|---|---|
Created seconds ago, live job | Concurrent run | Wait / -lock-timeout |
Created old, dead job | Stale lock | force-unlock <ID> |
ResourceNotFoundException | Missing DynamoDB table | Create table with LockID key |
AccessDenied / ConditionalCheckFailed on write | Missing IAM perms | Grant DynamoDB/S3 write |
| Two CI runs at once | Pipeline race | Concurrency group |
Frequently Asked Questions
Is it safe to run terraform force-unlock?
Only when you are certain no apply or plan is currently running against that state. Force-unlocking a live run removes the protection that prevents two processes from writing state at the same time, which can corrupt the state file. Confirm the lock holder is dead — check CI and the Created timestamp — before unlocking.
How do I find the lock ID to unlock?
It's printed in the Lock Info block of the error under ID:. If you've lost that output, re-run terraform plan to surface it again, or read the lock record directly from DynamoDB with aws dynamodb get-item on the LockID key for your state path.
Does this apply to OpenTofu too?
Yes. OpenTofu shares Terraform's state and locking model, so the causes and fixes are identical — just run tofu force-unlock <LOCK_ID> and tofu apply -lock-timeout=120s instead of the terraform equivalents. See OpenTofu vs Terraform for the broader differences.
Why does the lock keep coming back after I unlock it?
Because the underlying cause is still active. If a CI pipeline runs Terraform on every push without a concurrency group, each overlapping run re-creates the lock. Fix the root cause — add a concurrency group (Cause 5) and stop cancelling in-flight applies — rather than repeatedly force-unlocking.
Can I disable state locking to get unblocked?
You can pass -lock=false, but don't. It disables the exact safety check that prevents concurrent writes from corrupting your state, and it doesn't fix the real problem. Use -lock-timeout to wait for a busy lock, or force-unlock for a confirmed stale one instead.
See also
- Migrate a Terraform Project to OpenTofu (Safely, with State and CI)
- OpenTofu vs Terraform — how the fork compares, including shared state and locking behavior
- Terraform Import: Bring Existing Infrastructure Under Management — recover control of resources that drifted outside state
- Terraform EKS: Infrastructure as Code — a full backend + state setup in context
Still fighting state locks or a corrupted backend? Talk to us at Coding Protocols — we design Terraform state and CI workflows that don't step on themselves.
Official References
- Terraform documentation — configuration language, state and provider behaviour
- Terraform state — remote backends, locking and drift
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


