# Fix "Error Acquiring the State Lock" in Terraform

URL: https://www.devopsness.com/blog/fix-terraform-state-lock
Author: Kiril Urbonas
Published: 2026-07-31
Updated: 2026-10-31
Categories: DevOps
Tags: Terraform, Iac, Troubleshooting, State, DevOps

Summary: A stuck state lock stops every apply cold, and the fix is one command away once you confirm nobody else is actually running Terraform.

---
# Fix "Error Acquiring the State Lock" in Terraform

"Error acquiring the state lock" means another Terraform operation owns the state lock, or a run that crashed left a stale one behind. Read the `Who` and `Created` fields in the error, confirm nothing is running, then release it with `terraform force-unlock <LOCK_ID>`. Nothing in your infrastructure is broken; Terraform is refusing to proceed on purpose.

> [!VERIFIED]
> Terraform 1.17 (1.17.1, released 2026-10-28), against the developer.hashicorp.com state locking, `force-unlock` and S3 backend pages. Checked October 2026.

## What state locking is and why it exists

Terraform stores the mapping between your configuration and real resources in a state file. Any operation that could write state (`apply`, `destroy`, `import`, `state mv`) first takes a lock on the backend so only one writer runs at a time. The docs are blunt: if state locking fails, Terraform does not continue.

Without locking, two concurrent applies can interleave their writes and leave state describing resources that do not match reality, one of the hardest failure modes to recover from. The lock is taken at the start of the operation and released when it finishes cleanly. Most of the time you see this error, a previous run died before the release.

## What the error looks like

The message hands you everything you need. This one is from the S3 backend with a DynamoDB lock table:

```
$ terraform apply
Acquiring state lock. This may take a few moments...
╷
│ Error: Error acquiring the state lock
│
│ Error message: operation error DynamoDB: PutItem, ...
│ ConditionalCheckFailedException: The conditional request failed
│ Lock Info:
│   ID:        d8f3c1a2-9b7e-4f6a-8c21-2a5f0e9d7b41
│   Path:      my-tf-state/prod/terraform.tfstate
│   Operation: OperationTypeApply
│   Who:       kiril@build-agent-07
│   Version:   1.17.1
│   Created:   2026-10-29 14:02:11.884213 +0000 UTC
│   Info:
╵
```

Read the `Lock Info` block first. `Who` is the user and machine holding the lock, `Created` is its age, `Operation` is what it was doing. If `Who` is your own CI runner from twenty minutes ago and no job is active, the lock is stale. If `Who` is a colleague and the timestamp is thirty seconds old, someone is applying right now and you should wait.

## The three causes

**A previous run crashed or was interrupted.** Someone hit Ctrl-C mid-apply, a laptop slept, or a CI job hit its timeout and got killed. Terraform never reached the release step.

**A genuine concurrent run.** Two pipelines fired off the same branch, or a teammate is applying by hand while CI does the same. This is what the lock exists to catch. The fix is patience, not force.

**A network blip to the backend.** The lock was written, then the connection dropped before the release completed. The lock record persists though nothing is running.

## The safe fix

The order of operations matters more than the command.

1. Check whether anyone is running Terraform: your CI dashboard for in-flight jobs, your team channel, and the `Who` and `Created` fields against reality.
2. Only once you are confident nothing is live, release the lock with the exact ID from the error:

```
$ terraform force-unlock d8f3c1a2-9b7e-4f6a-8c21-2a5f0e9d7b41
Do you really want to force-unlock?
  ...
  Enter a value: yes

Terraform state has been successfully unlocked!
```

Run it from the same configuration directory, because the command removes the lock on the state for the current configuration. `-force` skips the prompt. Per the docs, `force-unlock` does not modify your infrastructure, and the lock ID acts as a nonce so you cannot release the wrong lock by accident.

## The danger of force-unlock

HashiCorp's locking page says to "be very careful with this command" and to use it only "to unlock your own lock in the situation where automatic unlocking failed." Unlock a lock that a live `apply` owns and you have invited a second writer into the state. Recovering can mean hand-editing JSON, re-importing resources, or restoring a backend object version, all under pressure. Never wire `force-unlock` into an automatic retry or an unattended pipeline step.

## The error strings by backend, and the last-resort cleanup

| Backend | What the lock error says | Where the lock lives |
|---|---|---|
| S3 + DynamoDB (`dynamodb_table`) | `ConditionalCheckFailedException: The conditional request failed` | An item in the table whose `LockID` is the state `Path` |
| S3 native (`use_lockfile = true`) | An S3 `PreconditionFailed` error on the lock object | A `<key>.tflock` object next to the state |
| GCS | An `Error 412` precondition failure on a `.tflock` object | A lock object in the state bucket |
| azurerm | `state blob is already locked` | A lease on the state blob |
| Local | `Local state cannot be unlocked by another process` | The local process; stop it instead |

When `force-unlock` itself cannot reach the backend, go to the store directly, and only after confirming no run is active. For DynamoDB, the lock item's `LockID` is the `Path` from Lock Info. Do not touch the separate `...-md5` item; that holds the state checksum, not the lock:

```
$ aws dynamodb get-item --table-name terraform-locks \
    --key '{"LockID": {"S": "my-tf-state/prod/terraform.tfstate"}}'
$ aws dynamodb delete-item --table-name terraform-locks \
    --key '{"LockID": {"S": "my-tf-state/prod/terraform.tfstate"}}'
```

For S3-native locking, the lock is an ordinary object:

```
$ aws s3 ls s3://my-tf-state/prod/
$ aws s3 rm s3://my-tf-state/prod/terraform.tfstate.tflock
```

On HCP Terraform, locking is per workspace and there is no table to edit. Unlock from the workspace UI or with `force-unlock`, and check who locked it first: humans can lock workspaces deliberately, and it looks the same.

## Prevention: move off DynamoDB, serialize CI, wait instead of failing

As of October 2026 the S3 backend docs mark DynamoDB-based locking as deprecated and say it "will be removed in a future minor version." Turn on S3-native locking; both arguments can be set together while you migrate:

```hcl
# backend.tf
terraform {
  backend "s3" {
    bucket       = "my-tf-state"
    key          = "prod/terraform.tfstate"
    region       = "eu-west-1"
    use_lockfile = true
  }
}
```

Then run `terraform init -reconfigure`. The role needs `s3:GetObject`, `s3:PutObject` and `s3:DeleteObject` on the `.tflock` key.

Serialize CI so only one job per state runs at a time. In GitHub Actions, a `concurrency` group queues runs instead of colliding (GitLab's equivalent is `resource_group`):

```yaml
# .github/workflows/terraform.yml
concurrency:
  group: terraform-prod
  cancel-in-progress: false
```

Keep `cancel-in-progress` false: cancelling a running apply is exactly how locks get orphaned. Finally, let Terraform wait out short overlaps instead of failing instantly:

```
$ terraform apply -lock-timeout=120s
```

For the broader catalog of plan, provider, and module failures, keep the [Terraform error troubleshooting guide](/blog/terraform-error-troubleshooting-guide) handy. If you are restructuring backends anyway, [migrating Terraform state](/blog/terraform-state-migration) covers the move.

## Where this breaks

Everything here assumes a backend that supports locking; not all do, and each backend's page says whether it does. If yours does not, the lock error never appears and concurrent runs simply corrupt state, so serializing CI is your only protection. And `force-unlock` cannot fix a state that two writers already mangled; that is a restore from a versioned backup, not an unlock.

## Questions people ask

### What does "Acquiring state lock. This may take a few moments" mean?

It is the normal message Terraform prints before any command that can write state, and it usually clears in a second. If it hangs and then fails with "Error acquiring the state lock", another process holds the lock or a dead run left it behind. With `-lock-timeout=120s` set, Terraform keeps retrying for that long before giving up instead of failing immediately.

### Is it safe to run terraform force-unlock?

Only after you confirm no operation is live. HashiCorp's locking docs say force unlock "should only be used to unlock your own lock in the situation where automatic unlocking failed." Unlocking someone else's active run lets two writers touch the same state, which is exactly the corruption locking prevents. The command needs the exact lock ID, and it never changes real infrastructure.

### Can I just use -lock=false?

You can for most commands, but HashiCorp's docs say they do not recommend it, and it skips the safety check rather than clearing the stale lock. It can be tolerable for a read-only `plan` on a state nobody else is touching. For `apply`, release the stale lock properly with `terraform force-unlock` and the ID from the error.

## The call we'd make

Treat a lock error as a question, not an emergency. Read the `Lock Info`, confirm from CI and your team that nothing is running, then `force-unlock` with the exact ID. Reach for backend-level deletion only when `force-unlock` cannot talk to the backend. Then spend ten minutes switching to `use_lockfile`, adding a concurrency group, and setting `-lock-timeout`, so the same interruption does not page you next week.

## Sources

- [Terraform docs: State locking](https://developer.hashicorp.com/terraform/language/state/locking)
- [Terraform docs: terraform force-unlock command](https://developer.hashicorp.com/terraform/cli/commands/force-unlock)
- [Terraform docs: S3 backend (use_lockfile and DynamoDB locking)](https://developer.hashicorp.com/terraform/language/backend/s3)
- [Terraform releases on GitHub](https://github.com/hashicorp/terraform/releases)

