/인프라/Fixing Terraform State Lock Errors: force-unlock and DynamoDB Lock Deletion
InfrastructureTerraformstate lock

Fixing Terraform State Lock Errors: force-unlock and DynamoDB Lock Deletion

Diagnose Terraform "Error acquiring the state lock" with a log reading table, then safely release it with force-unlock or DynamoDB lock deletion. A copy-paste command set that also prevents recurrence with lock-timeout and CI concurrency se

Fixing Terraform State Lock Errors: force-unlock and DynamoDB Lock Deletion

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.

CODE
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.

FieldExample valueWhat it tells you / how to judge
IDa1b2c3d4-...567890The lock ID you pass straight into force-unlock. This value becomes the command argument.
Pathmy-bucket/env/prod/terraform.tfstateThe S3 key path that is locked. Confirms which environment (prod/stg) is blocked. Also used as LockID when querying DynamoDB directly.
OperationOperationTypeApplyWhether it died during apply or plan. If it broke mid-apply, part of state may already have been updated—be extra careful.
Whouser@hostnameWho / 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.
Created2026-06-14 09:12:33 UTCWhen 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.
Version1.7.5Terraform 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.

CauseTypical symptomsFirst checkAction
Abnormal exit of a previous run (Ctrl+C, OOM kill)Who is you, Created is oldCreated is far from nowforce-unlock is safe
Concurrent CI collisionWho is a CI runner, another job is running at the same timeWhether the same workflow is running twice in the pipelineWait for the job to finish, then add concurrency control
Network dropApply hung after the connection droppedtimeout / connection reset in runner or local logsConfirm the runner is dead, then unlock + retry
Insufficient DynamoDB permissionsAccompanied by AccessDeniedExceptionReview the IAM policyGrant dynamodb:GetItem/PutItem/DeleteItem
S3 backend misconfigurationMissing or mistyped dynamodb_tableValidate the backend blockFix 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 terraform process exists in another terminal.
Bash
ps aux | grep terraform | grep -v grep

If 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.

Bash
# 반드시 다른 사람/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 plan after 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.

Bash
# 현재 락 항목 조회 (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.tfstatethe actual lock item. This is what you delete.
  • my-bucket/env/prod/terraform.tfstate-md5checksum (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

Bash
# 락이 잡혀 있으면 바로 실패하지 말고 120초까지 재시도하며 대기
terraform apply -lock-timeout=120s

Brief overlapping concurrent runs will naturally queue up with this one line.

5-2. Standardize backend config

HCL
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:

YAML
concurrency:
  group: terraform-prod
  cancel-in-progress: false   # 진행 중 잡을 죽이지 말고 줄 세우기

GitLab CI — serialize jobs on the same resource with resource_group:

YAML
deploy_prod:
  stage: deploy
  resource_group: terraform-prod
  script:
    - terraform apply -auto-approve -lock-timeout=120s

Those 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.

HCL
terraform {
  backend "s3" {
    bucket       = "my-bucket"
    key          = "env/prod/terraform.tfstate"
    region       = "ap-northeast-2"
    use_lockfile = true   # DynamoDB 불필요
    encrypt      = true
  }
}
CategoryDynamoDBS3 native (use_lockfile)
Extra resourcesDynamoDB table requiredS3 bucket only
Supported versionsAll versionsTerraform 1.10+
Unlock methodforce-unlock / delete-itemforce-unlock / delete the S3 .tflock object

📌 OpenTofu users: swap terraform force-unlock for tofu force-unlock and 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.

확인 정보
✦ ✦ ✦
편집 검토 · Editorial Review

Nodelog는 모든 콘텐츠의 내용과 출처를 공개 전에 검토합니다. 환경(OS·버전)에 따라 결과가 달라질 수 있는 기술 정보는 공식 문서와 함께 확인하며, 검토 기준과 정정 원칙은 편집 정책에서 안내합니다. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

편집 책임 · Nodelog 기술 편집팀·발행 · ·업데이트 ·

Comments

Be the first to comment.