Terraform

Terraform. Error acquiring the state lock

Terraform will not run because the state is locked. When force-unlock is safe, when it will corrupt your state, and how to stop it recurring.

medium fix6 min read

terraform. The error
Error: Error acquiring the state lock

Error message: operation error DynamoDB: PutItem, ConditionalCheckFailedException
Lock Info:
  ID:        8f3c1e2a-4b7d-11ef-9c2a-0242ac120002
  Path:      acme-tfstate/production/terraform.tfstate
  Operation: OperationTypeApply
  Who:       alice@laptop
  Created:   2026-09-16 09:14:22.481 +0000 UTC

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Wait for it, because most locks are not stale

    terraform apply -lock-timeout=10m

    Waits instead of failing immediately. A CI run or a colleague's apply usually finishes inside that window. The error names Who and Created. Ask that person before you break anything.

  2. 2

    Only once you are certain nothing is running, break it

    terraform force-unlock 8f3c1e2a-4b7d-11ef-9c2a-0242ac120002

    The ID comes from the error message. Breaking a lock while an apply is genuinely in flight can corrupt state. This is the one genuinely dangerous step on the page.

  3. 3

    If state is already damaged, recover from a version

    aws s3api list-object-versions --bucket acme-tfstate --prefix production/terraform.tfstate --query 'Versions[:5].[VersionId,LastModified,Size]' --output table

    With bucket versioning on, download the last good version and terraform state push it. Without versioning there is no undo at all, so turn it on before you need it.

All 9 sections

Terraform locks state before any operation that could write to it, so two applies cannot run at once and corrupt each other. This error means the lock is already held.

The lock is doing its job far more often than it is broken. Before reaching for force-unlock, work out which situation you are in, because breaking a live lock is how state actually gets corrupted.

Read the lock info first

Terraform prints everything you need:

Lock Info:
  ID:        8f3c1e2a-4b7d-11ef-9c2a-0242ac120002
  Operation: OperationTypeApply
  Who:       alice@laptop
  Created:   2026-09-16 09:14:22.481 +0000 UTC

Three fields decide what to do:

FieldWhat it tells you
WhoWhose machine or which CI runner holds it
CreatedHow long it has been held
OperationOperationTypeApply is far more serious than OperationTypePlan

A lock created two minutes ago by a colleague running an apply is not stuck. Wait.

A lock created three hours ago by a CI runner that no longer exists is stuck, and safe to break once you have confirmed nothing is running.

When it is genuinely a live lock

Someone or something is mid-operation. Options, in order:

Wait. Most applies finish in minutes.

Wait automatically, which is the right default in CI:

terraform apply -lock-timeout=10m

Terraform retries for that long before failing. Two pipelines that would otherwise collide simply queue.

Ask the person named in Who. Obvious, and routinely skipped.

When the lock is genuinely stuck

A process killed mid-operation: a cancelled CI job, a closed laptop, a network drop, Ctrl-C at the wrong moment. Leaves the lock behind with nothing holding it.

Before unlocking, establish that nothing is running. Specifically:

  • Check your CI system for a running job on that workspace.
  • Ask the person in Who whether they are mid-apply.
  • If the timestamp is minutes old and you cannot account for it, wait rather than guess.

Then:

terraform force-unlock 8f3c1e2a-4b7d-11ef-9c2a-0242ac120002

The ID comes from the error message. Terraform asks for confirmation, and -force skips the prompt, which you should not use interactively.

Why breaking a live lock is dangerous

If an apply genuinely is running and you force-unlock, a second apply can start against the same state. Both write to it, and the one that finishes last overwrites the other's record.

The result is state that does not match reality: resources exist that Terraform does not know about, and resources in state that were never created. Recovering means importing and removing entries by hand, and on a large configuration that is a genuinely bad day.

This is the one Terraform operation where being slow is correct.

Recovering if state is already corrupted

Enable versioning on your state bucket before you need it, that is the difference between a five-minute recovery and a rebuild.

aws s3api list-object-versions \
  --bucket acme-tfstate \
  --prefix production/terraform.tfstate \
  --query 'Versions[:5].[VersionId,LastModified,Size]' --output table
aws s3api get-object \
  --bucket acme-tfstate \
  --key production/terraform.tfstate \
  --version-id <good-version> \
  restored.tfstate

terraform state push restored.tfstate

Then run a plan and read it extremely carefully before applying anything.

Backend-specific notes

S3 with native locking: modern Terraform supports use_lockfile = true, which stores a lock object in S3 itself and removes the DynamoDB requirement:

terraform {
  backend "s3" {
    bucket       = "acme-tfstate"
    key          = "production/terraform.tfstate"
    region       = "eu-west-1"
    encrypt      = true
    use_lockfile = true
  }
}

S3 with DynamoDB: the older pattern. ConditionalCheckFailedException in the error means the lock row already exists. You can see it directly:

aws dynamodb get-item \
  --table-name terraform-locks \
  --key '{"LockID":{"S":"acme-tfstate/production/terraform.tfstate"}}'

Deleting that row by hand works and is strictly worse than force-unlock, which performs the same operation with a confirmation prompt and correct handling of the digest entry.

Terraform Cloud / HCP: locks are managed in the UI, and a run can be discarded there. force-unlock does not apply.

Stopping it recurring

MeasureEffect
-lock-timeout=10m in CIConcurrent pipelines queue instead of failing
Pipeline concurrency limitsOnly one run per workspace at a time
Separate state per environmentA staging run cannot block production
Bucket versioningRecovery is possible if a lock is broken badly
Avoid Ctrl-C during applyLet it finish, or expect a stuck lock

The concurrency control is the highest-value one. A pipeline configured so two merges cannot apply the same workspace simultaneously removes the common cause entirely, and queueing at the CI layer gives a clearer outcome than a lock timeout.

A checklist

  1. Read Who, Created and Operation from the lock info.
  2. Created minutes ago by a real person or a running job → wait.
  3. Check CI for a running job on this workspace.
  4. Ask the person named in Who.
  5. Only once you are certain nothing is running: terraform force-unlock <ID>.
  6. Run a plan afterwards and read it before applying.
  7. Add -lock-timeout and pipeline concurrency limits so it stops happening.

Frequently Asked Questions

What does "Error acquiring the state lock" mean?

Terraform locks the state file before any operation that could write to it, so two runs cannot modify it simultaneously and corrupt each other. This error means something already holds that lock, usually a colleague or a CI job that is genuinely mid-operation, and sometimes a process that died and left the lock behind. The lock info Terraform prints tells you which: check who holds it, when it was created, and what operation it was performing.

Is terraform force-unlock safe?

Only when you have confirmed nothing is actually running. Breaking a live lock allows a second apply to start against the same state, and whichever finishes last overwrites the other's record. Leaving state that does not match reality, with resources missing from it and entries for things that were never created. Recovering means importing and removing state entries by hand. Check your CI system and ask whoever is named in the lock before running it.

How do I find the lock ID for force-unlock?

Terraform prints it in the error message under Lock Info as ID. Copy that value into terraform force-unlock <ID>. If you have lost the output, running any Terraform command against the workspace produces the error again with the same ID. For a DynamoDB backend you can also read the lock row directly with aws dynamodb get-item, though force-unlock is the correct tool because it handles the digest entry properly.

How do I prevent state lock errors in CI/CD?

Set -lock-timeout=10m on plan and apply so a run waits for the lock rather than failing immediately, which makes concurrent pipelines queue. Add concurrency controls at the CI layer so only one run per workspace executes at a time, which is the more reliable fix since queueing there gives a clearer outcome than a timeout. Keeping separate state per environment also helps, since a staging run then cannot block a production one.

Can I just delete the DynamoDB lock row?

It works, and terraform force-unlock is better. The command does the same thing with a confirmation prompt, validates the lock ID you supply against the one actually held, and handles the accompanying digest entry correctly. Deleting rows by hand risks removing the wrong one or leaving the digest inconsistent. Either way, the important step is the same: confirm nothing is genuinely running first.

Do I still need a DynamoDB table for Terraform state locking?

Not on recent versions. Terraform now supports native S3 locking via use_lockfile = true in the backend block, which stores a lock object alongside the state and removes the separate table entirely. One less resource to provision, pay for and manage. The DynamoDB pattern remains supported and is what older configurations use, so you will encounter both. If you are setting up a new backend, prefer the native option.

Learn the underlying concept