Terraform Error Guide: 'Error acquiring the state lock' on plan/apply
Fix Terraform's 'Error acquiring the state lock': diagnose stale DynamoDB/blob locks, abandoned CI runs, expired credentials, and safely force-unlock state.
- #terraform
- #troubleshooting
- #errors
- #state
Overview
Terraform takes a lock on the state before it writes to it, so two concurrent runs cannot corrupt the same state file. When the backend already holds a lock — from a still-running apply, a crashed process, or a CI job that died mid-run — the new run cannot acquire it and aborts before doing any work.
You will see this on terraform plan, apply, or destroy:
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: 9f1c4e2a-7b30-4a55-9d21-6f0e8b3c4d12
Path: my-tf-state/prod/network/terraform.tfstate
Operation: OperationTypePlan
Who: runner@ci-build-4821
Version: 1.7.5
Created: 2026-06-23 13:41:02.118 +0000 UTC
Info:
The lock is held in the backend, not locally: an S3 backend uses a DynamoDB item, an azurerm backend uses a blob lease, and gcs uses an object generation. The Who, Created, and Operation fields tell you who holds it and how stale it is.
Symptoms
plan/apply/destroyfail immediately withError acquiring the state lock.- The
Lock Infoblock names another user, CI runner, or a run from minutes/hours ago. - A previous run was Ctrl-C’d, lost network, or the CI pod was evicted.
- Two pipelines target the same workspace at the same time.
terraform plan
Error: Error acquiring the state lock
Lock Info:
ID: 9f1c4e2a-7b30-4a55-9d21-6f0e8b3c4d12
Operation: OperationTypeApply
Who: jane@laptop
Created: 2026-06-23 13:12:44 +0000 UTC
Common Root Causes
1. A genuinely concurrent run holds the lock
Another engineer or pipeline is mid-apply against the same state right now. This is the lock working as designed — not an error to force past.
terraform plan 2>&1 | grep -A6 "Lock Info"
Lock Info:
Operation: OperationTypeApply
Who: deploy-bot@ci-build-4905
Created: 2026-06-23 14:02:55 +0000 UTC
A Created time of seconds ago plus OperationTypeApply means wait, don’t unlock.
2. A crashed or cancelled run left a stale lock
A Ctrl-C, evicted CI pod, or lost VPN during apply leaves the lock behind because Terraform never reached the unlock step.
aws dynamodb get-item \
--table-name terraform-locks \
--key '{"LockID":{"S":"my-tf-state/prod/network/terraform.tfstate"}}'
{
"Item": {
"LockID": {"S": "my-tf-state/prod/network/terraform.tfstate"},
"Info": {"S": "{\"ID\":\"9f1c4e2a-...\",\"Created\":\"2026-06-23T13:41:02Z\"}"}
}
}
If Created is hours old and no process is running, the lock is stale.
3. Two pipelines share one state/workspace
Separate CI jobs (e.g. PR validation and a merge deploy) both run against the same backend key with no serialization, so they race for the lock.
grep -A4 'backend "s3"' backend.tf
backend "s3" {
bucket = "my-tf-state"
key = "prod/network/terraform.tfstate"
dynamodb_table = "terraform-locks"
}
Two jobs with the same key will collide. Distinct workspaces or keys avoid it.
4. The lock table or lease backend is misconfigured
If dynamodb_table is missing on an S3 backend, locking is silently skipped — but on azurerm, a broken blob lease can leave a permanent lease that looks like a stuck lock.
az storage blob show \
--container-name tfstate --name prod.terraform.tfstate \
--account-name mytfstate --query "properties.lease" -o json
{
"state": "leased",
"status": "locked"
}
A leased/locked blob with no active run is a stale lease that must be broken.
5. Expired credentials abort apply mid-write
A short-lived STS/session token expires during a long apply, the process dies after locking but before unlocking, and the next run finds a stale lock.
aws sts get-caller-identity
An error occurred (ExpiredToken) when calling the GetCallerIdentity operation:
The security token included in the request is expired.
Refresh credentials first, otherwise even force-unlock may fail to reach the backend.
6. Clock skew or wrong backend key
Pointing at the wrong key/workspace shows a lock from an unrelated state, making it look held when your real state is fine.
terraform workspace show
terraform state list | head -1
default
Confirm you are operating on the state you think you are before unlocking anything.
Diagnostic Workflow
Step 1: Read the full lock info
terraform plan 2>&1 | sed -n '/Lock Info/,/^$/p'
Note Who, Operation, Created, and the lock ID — you need the ID to unlock.
Step 2: Decide if the run is live or dead
# CI: check whether the named runner/job is still active
gh run list --workflow deploy.yml --limit 5
If the Who job is still in_progress, wait. If it failed/cancelled, the lock is stale.
Step 3: Inspect the backend lock directly
# S3 + DynamoDB
aws dynamodb get-item --table-name terraform-locks \
--key '{"LockID":{"S":"<STATE_PATH>"}}'
# azurerm
az storage blob show --container-name tfstate --name <KEY> \
--account-name <ACCT> --query "properties.lease"
Confirm the lock’s age and that no process owns it.
Step 4: Refresh credentials
aws sts get-caller-identity || aws sso login
Make sure you can actually reach the backend before unlocking — a failed unlock can leave you worse off.
Step 5: Force-unlock with the exact lock ID, then retry
terraform force-unlock 9f1c4e2a-7b30-4a55-9d21-6f0e8b3c4d12
terraform plan
Only run force-unlock once you have confirmed no live run holds the lock.
Example Root Cause Analysis
A nightly deploy pipeline fails with Error acquiring the state lock on prod/network. The lock info reads:
Lock Info:
Operation: OperationTypeApply
Who: runner@ci-build-4821
Created: 2026-06-23 02:14:09 +0000 UTC
The named job ci-build-4821 is checked in CI and shows status cancelled — the runner was scaled down mid-apply at 02:14. No other run is active:
gh run list --workflow deploy.yml --limit 3
completed failure deploy ci-build-4905
cancelled cancelled deploy ci-build-4821
This is a stale lock from the cancelled job, not a live conflict. Credentials are valid:
aws sts get-caller-identity
{"Account": "123456789012", "Arn": "arn:aws:sts::123456789012:assumed-role/deploy/..."}
Fix: force-unlock the stale ID and re-run.
terraform force-unlock 9f1c4e2a-7b30-4a55-9d21-6f0e8b3c4d12
terraform apply -auto-approve
The apply proceeds normally. The deeper fix is to serialize the pipeline so a new run cannot start while the previous one is unfinished.
Prevention Best Practices
- Never
force-unlockreflexively. Confirm the namedWho/job is actually dead first — unlocking a live apply can corrupt state. - Serialize CI runs against each state with a concurrency group (e.g. one in-flight job per workspace) so pipelines never race for the lock.
- Use long-lived enough credentials (or auto-refresh) so a long apply cannot expire mid-run and orphan a lock.
- Set a backend timeout and prefer
terraform applyover Ctrl-C-prone interactive runs in automation; trapping signals to unlock cleanly helps. - Keep one
key/workspace per logical environment so unrelated runs never share a lock. - For triage, the free incident assistant can turn a lock-info block into a wait-or-unlock recommendation. More patterns in the Terraform guides.
Quick Command Reference
# See the full lock info
terraform plan 2>&1 | sed -n '/Lock Info/,/^$/p'
# Inspect the backend lock (S3 + DynamoDB)
aws dynamodb get-item --table-name terraform-locks \
--key '{"LockID":{"S":"<STATE_PATH>"}}'
# Inspect the lease (azurerm)
az storage blob show --container-name tfstate --name <KEY> \
--account-name <ACCT> --query "properties.lease"
# Confirm the named CI job is dead, not live
gh run list --workflow deploy.yml --limit 5
# Refresh credentials before unlocking
aws sts get-caller-identity || aws sso login
# Force-unlock with the exact ID (only when no live run holds it)
terraform force-unlock <LOCK_ID>
Conclusion
Error acquiring the state lock means the backend already holds a lock on your state. The usual root causes:
- A genuinely concurrent run holds it — wait, do not unlock.
- A crashed/cancelled run left a stale lock behind.
- Two pipelines share one
key/workspace and race for the lock. - A misconfigured lock table or stuck blob lease.
- Expired credentials killed an apply after it locked but before it unlocked.
- You are pointed at the wrong
key/workspace and seeing an unrelated lock.
Always read the lock info, confirm the holder is truly dead, then force-unlock with the exact ID. The durable fix is serializing runs so the lock is never abandoned.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.