How to Fix Terraform "Error acquiring the state lock": From force-unlock to DynamoDB Lock Deletion
Pipeline frozen and every second counts? Skip the concepts and jump straight to 4. Safe unlock procedure. That said, we strongly recommend checking just one row of the section 2 reading table before you run force-unlock. Unlock the wrong lock and you will corrupt state.
If terraform apply printed Error acquiring the state lock in red, someone (or another CI job, or you yourself after a Ctrl+C 30 minutes ago) still holds a lock on the same state. Blindly running force-unlock is the most common mistake. If the lock belongs to a still-running job, forcing it off lets two processes write state at once and your infrastructure gets tangled.
This post takes you from reading the error log in 5 seconds → branching on the cause → safe unlock → preventing recurrence, using nothing but copy-paste commands and tables.
1. Error log reading table — 5-second diagnosis
Terraform lock errors usually look like this.
Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional
request failed
Lock Info:
ID: a1b2c3d4-5e6f-7890-abcd-ef1234567890
Path: my-bucket/env/prod/terraform.tfstate
Operation: OperationTypeApply
Who: user@hostname
Version: 1.7.5
Created: 2026-06-14 09:12:33.123456 +0000 UTC
Info:Break this block down field by field and the answer is almost obvious.
| Field | Example value | What it tells you / how to judge |
|---|---|---|
| ID | a1b2c3d4-...567890 | The lock ID you pass straight into force-unlock. This value becomes the command argument. |
| Path | my-bucket/env/prod/terraform.tfstate | The S3 key path that is locked. Confirms which environment (prod/stg) is blocked. Also used as LockID when querying DynamoDB directly. |
| Operation | OperationTypeApply | Whether it died during apply or plan. If it broke mid-apply, part of state may already have been updated—be extra careful. |
| Who | user@hostname | Who / which CI runner grabbed it. Your hostname means a previous run of yours; runner-xxx means a CI job. If it is a coworker you do not recognize, ask them first. |
| Created | 2026-06-14 09:12:33 UTC | When it was acquired. A large gap from now (e.g. 30 minutes ago) means a high chance it is a ghost lock left by an abnormal exit. |
| Version | 1.7.5 | Terraform version that took the lock. Useful reference when debugging version skew on the team. |
The key is the Who + Created combination. "My hostname + a long time ago" is almost always a ghost lock. "Another CI runner + just now" is a live job, so do not touch it—wait.
2. Cause-by-cause diagnosis — is my lock a ghost, or still alive?
If the reading table narrowed it down, it falls into one of these five.
| Cause | Typical symptoms | First check | Action |
|---|---|---|---|
| Abnormal exit of a previous run (Ctrl+C, OOM kill) | Who is you, Created is old | Created is far from now | force-unlock is safe |
| Concurrent CI collision | Who is a CI runner, another job is running at the same time | Whether the same workflow is running twice in the pipeline | Wait for the job to finish, then add concurrency control |
| Network drop | Apply hung after the connection dropped | timeout / connection reset in runner or local logs | Confirm the runner is dead, then unlock + retry |
| Insufficient DynamoDB permissions | Accompanied by AccessDeniedException | Review the IAM policy | Grant dynamodb:GetItem/PutItem/DeleteItem |
| S3 backend misconfiguration | Missing or mistyped dynamodb_table | Validate the backend block | Fix the config, then terraform init -reconfigure |
Common pattern: In the field, most lock errors come from the first two causes. A ghost lock left by a job that OOM-killed overnight, or two applies overlapping because consecutive merges to main fired back-to-back. That is why a single line of CI concurrency control (section 5) has been by far the highest-ROI root fix.
3. Confirm the lock is actually alive first
Before force-unlock, always cross-check whether the runner/person in Who is actually running.
- If it is a CI runner: In GitHub Actions / GitLab, check whether that job is still
running. If it is running, never unlock—wait for it to finish. - If it is your local machine: Check whether a live
terraformprocess exists in another terminal.
ps aux | grep terraform | grep -v grepIf there is no process and every CI job has finished, the lock is a ghost. Proceed to section 4 with confidence.
4. Safe unlock procedure (copy-paste ready)
4-1. First choice: terraform force-unlock
Paste the ID value from the reading table as-is.
# 반드시 다른 사람/CI가 실행 중이 아님을 확인한 뒤!
terraform force-unlock a1b2c3d4-5e6f-7890-abcd-ef1234567890
# 자동화/비대화형(CI) 환경에서 확인 프롬프트 없이
terraform force-unlock -force a1b2c3d4-5e6f-7890-abcd-ef1234567890⚠️ Risk warning Forcing off a lock that belongs to a still-running job can let two processes write state at once, corrupting the state file, or duplicate-create / duplicate-delete resources. Use force-unlock only when you are certain that job is dead. If the lock broke mid-apply, always run
terraform planafter unlocking and inspect drift against real state first.
4-2. Last resort: delete the DynamoDB lock item directly
When force-unlock does not work because of an ID mismatch or a broken backend, you touch the DynamoDB item directly.
# 현재 락 항목 조회 (LockID = "<bucket>/<key>" 형식)
aws dynamodb get-item \
--table-name terraform-locks \
--key '{"LockID":{"S":"my-bucket/env/prod/terraform.tfstate"}}'
# 락 항목 강제 삭제 (force-unlock이 안 먹힐 때 최후 수단)
aws dynamodb delete-item \
--table-name terraform-locks \
--key '{"LockID":{"S":"my-bucket/env/prod/terraform.tfstate"}}'Watch the -md5 suffix: DynamoDB usually contains two kinds of items.
my-bucket/env/prod/terraform.tfstate→ the actual lock item. This is what you delete.my-bucket/env/prod/terraform.tfstate-md5→ checksum (digest) item for the state file. Used for integrity checks—do not delete it. Deleting it can cause state verification warnings on the next run.
So if you only want to release the lock, delete-item only the LockID without -md5.
5. Recurrence prevention checklist
Once you have been through it, real skill is making sure it never happens again.
5-1. Wait instead of failing immediately with lock-timeout
# 락이 잡혀 있으면 바로 실패하지 말고 120초까지 재시도하며 대기
terraform apply -lock-timeout=120sBrief overlapping concurrent runs will naturally queue up with this one line.
5-2. Standardize backend config
terraform {
backend "s3" {
bucket = "my-bucket"
key = "env/prod/terraform.tfstate"
region = "ap-northeast-2"
dynamodb_table = "terraform-locks"
encrypt = true
}
}If you omit dynamodb_table, locking never happens at all and you get concurrent-write accidents. Always separate key per environment.
5-3. CI concurrency control — the most effective root fix
GitHub Actions — serialize duplicate runs of the same workflow:
concurrency:
group: terraform-prod
cancel-in-progress: false # 진행 중 잡을 죽이지 말고 줄 세우기GitLab CI — serialize jobs on the same resource with resource_group:
deploy_prod:
stage: deploy
resource_group: terraform-prod
script:
- terraform apply -auto-approve -lock-timeout=120sThose two lines eliminate the "two applies overlap from consecutive merges to main" lock-collision scenario.
5-4. Note: S3 native locking without DynamoDB
From Terraform 1.10+, use_lockfile = true lets you keep a lock file on S3 itself without a DynamoDB table.
terraform {
backend "s3" {
bucket = "my-bucket"
key = "env/prod/terraform.tfstate"
region = "ap-northeast-2"
use_lockfile = true # DynamoDB 불필요
encrypt = true
}
}| Category | DynamoDB | S3 native (use_lockfile) |
|---|---|---|
| Extra resources | DynamoDB table required | S3 bucket only |
| Supported versions | All versions | Terraform 1.10+ |
| Unlock method | force-unlock / delete-item | force-unlock / delete the S3 .tflock object |
📌 OpenTofu users: swap
terraform force-unlockfortofu force-unlockand it applies the same way. DynamoDB/S3 CLI commands stay as-is.
References: official docs
The primary source for the behavior, settings, and errors covered in this post is the official documentation below. Check there for version-specific options and exact behavior.
FAQ
Q. I ran force-unlock and still get "Error acquiring the state lock".
A. Suspect two things. ① Another CI job is still running and recreating the lock — stop that job first. ② force-unlock could not delete the real item because of a backend ID mismatch — confirm the LockID with aws dynamodb get-item from 4-2, then delete-item.
Q. Should I also delete the DynamoDB item with the -md5 suffix?
A. No. -md5 is the state integrity checksum—do not delete it. To release the lock, delete only the LockID item without -md5.
Q. I unlocked after apply died mid-run and the infrastructure looks shaky. What should I do first?
A. Right after unlocking, run terraform plan and inspect drift between real resources and state. Apply may have landed only halfway, so re-apply only after you have reviewed the plan.
Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.
Comments
Be the first to comment.