Study guide

11.2 State Locking

HashiCorp Certified: Terraform Associate 004

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Format, Delivery, Retakes, and Recertification 1.3 004 Changes, Terraform 1.12, and Study Plan

2 Infrastructure as Code with Terraform

2.1 What Infrastructure as Code Is 2.2 Advantages of IaC Patterns 2.3 Multi-Cloud, Hybrid, and Service-Agnostic Workflows

3 Terraform Fundamentals: Providers & State

3.1 Install and Version Terraform Providers 3.2 How Terraform Uses Providers 3.3 Configuration with Multiple Providers 3.4 How Terraform Uses and Manages State

4 Core Terraform Workflow: Init & Validate

4.1 The Terraform Write–Plan–Apply Workflow 4.2 Initialize a Working Directory 4.3 Validate a Configuration

5 Plan, Apply, Destroy, and Formatting

5.1 Generate and Review an Execution Plan 5.2 Apply Changes to Infrastructure 5.3 Destroy Terraform-Managed Infrastructure 5.4 Formatting and Style Adjustments

6 Resources, Data Sources, and References

6.1 Resource Blocks vs Data Blocks 6.2 Resource Attributes and Cross-Resource References 6.3 Resource Addressing and Interpolation Patterns

7 Variables, Outputs, and Complex Types

7.1 Input Variables, tfvars, and Precedence 7.2 Output Values 7.3 Complex Types: list, map, object, tuple, set

8 Dynamic Configuration and Dependencies

8.1 Expressions, count, for_each, and dynamic Blocks 8.2 Built-in Functions 8.3 Dependencies, depends_on, and Lifecycle Rules

9 Conditions, Secrets, and Sensitive Data

9.1 Custom Conditions: validation, precondition, postcondition, check 9.2 Sensitive Data Handling 9.3 Vault, Ephemeral Values, and Write-Only Arguments

10 Terraform Modules

10.1 How Terraform Sources Modules 10.2 Variable Scope within Modules 10.3 Using Modules in Configuration 10.4 Managing Module Versions

11 Terraform State Management

11.1 The Local Backend 11.2 State Locking 11.3 Remote State and the backend Block 11.4 Resource Drift and State Operations

12 Maintain Infrastructure with Terraform

12.1 Import Existing Infrastructure 12.2 Inspect State with the CLI 12.3 Verbose Logging and Diagnostics

13 HCP Terraform

13.1 Create Infrastructure with HCP Terraform 13.2 Collaboration and Governance Features 13.3 Workspaces and Projects 13.4 HCP Terraform Integrations

11.2 State Locking

Key Takeaways Explain This Topic AI

If the backend supports locking, Terraform locks state automatically on every operation that could write state; a failed lock stops the command

You normally see no lock message; a status line appears only when acquiring the lock takes longer than expected

Not every backend locks, and they do not lock equally: local uses system APIs, S3 locking is opt-in, http locking needs lock_address

On Terraform 1.12 the S3 backend enables native locking with use_lockfile = true (default false); dynamodb_table is deprecated

terraform force-unlock LOCK_ID is a last resort for a lock you own after automatic unlock failed; it does not change infrastructure

Last updated: August 2026

11.2 State Locking

Quick Answer: If the backend supports it, Terraform automatically locks state for every operation that could write state. You usually see no message. If the lock cannot be acquired, the command stops . Not every backend locks, and S3 locking is opt-in . terraform force-unlock LOCK_ID is a last resort for a lock you own after automatic unlock failed.

Objective 6b on Terraform Associate (004) is about preventing concurrent writers. Official references: State Locking , terraform force-unlock , and the Terraform 1.12 S3 backend page.

Why a lock exists

State is the binding database. Two applies that read the same snapshot, each create an object, and each write a new snapshot will lose one writer's bindings. The leftover remote object is unmanaged. The overwritten snapshot may point at objects the other writer already replaced. Locking serializes writers so only one process updates that snapshot at a time.

HashiCorp's locking page states the rule in one paragraph:

If the backend supports locking, Terraform locks state for all operations that could write state .

Locking happens automatically . You do not see a message that it happened.

If state locking fails , Terraform does not continue .

You can disable locking on most commands with -lock=false . HashiCorp does not recommend it.

If acquiring the lock takes longer than expected, Terraform prints a status message . Silence does not mean "no lock" — it means the lock was acquired quickly, or the backend does not lock.

Read-only commands that never write state do not need the same exclusive lock. terraform state list and terraform state show are inspection. terraform apply , terraform destroy , terraform refresh / refresh-only apply, and mutating terraform state subcommands ( mv , rm , replace-provider ) are writers. # Dangerous: skip the lock. Do not make this a habit.

terraform apply -lock=false

# Wait up to two minutes if another run still holds the lock.

terraform apply -lock-timeout=2m

-lock-timeout retries acquisition. The default on terraform init is 0s : fail immediately if the lock is already held. A timeout is how a pipeline waits for a teammate's apply to finish. It is not a reason to use -lock=false .

Backends do not lock equally

HashiCorp: not all backends support locking . You must read the page for that backend. On Terraform 1.12 the built-in types are local , remote , s3 , azurerm , gcs , http , consul , kubernetes , oss , cos , oci , and pg . You cannot load extra backends as plugins.

Backend (1.12 docs) Locking quality you should remember

local Locks with system APIs on that machine. Two processes in one directory are serialized. Two laptops are not. Another process cannot force-unlock a local file.

s3 Locking is opt-in . Native S3 locking is use_lockfile = true (default false ). dynamodb_table still works and is deprecated .

azurerm Supports locking and consistency checking with Azure Blob Storage native capabilities.

http Locking is optional . You must set lock_address / unlock_address . Without those, HTTP storage does not lock.

consul , gcs , kubernetes , remote Documented as backends that can lock; still verify the type's page for the flags that type requires.

The exam trap is treating "we use S3, therefore we lock" as a fact. On Terraform 1.12, a bare backend "s3" { bucket, key, region } stores state and does not lock until you turn locking on.

S3 locking on Terraform 1.12 (verify this, do not recycle old blogs)

The official v1.12.x S3 page says:

State locking is an opt-in feature of the S3 backend.

Locking can be enabled via S3 or DynamoDB .

DynamoDB-based locking is deprecated and will be removed in a future minor version.

To migrate from older Terraform that only knew DynamoDB locking, you may configure S3 and DynamoDB arguments at the same time .

Native S3 locking: use_lockfile (optional, defaults to false ) writes a lock object next to the state key, named <key>.tflock .

  • Deprecated DynamoDB locking: dynamodb_table naming a table whose partition key is LockID (String). terraform {
  • backend "s3" {
  • bucket = "tfstate-prod-004"
  • key = "network/terraform.tfstate"
  • region = "us-east-1"
  • use_lockfile = true
  • }
  • }

Historically, teams added a DynamoDB table because S3 alone did not lock. That history is why so many older study notes say "S3 plus DynamoDB." For 004 / Terraform 1.12, quote the current page: prefer use_lockfile = true ; treat dynamodb_table as the deprecated migration path; do not claim S3 locks by default.

IAM for native locking also needs s3:GetObject , s3:PutObject , and s3:DeleteObject on the .tflock object. Those permissions are not required on the state object for delete — Terraform does not delete the state file. Enable bucket versioning so a bad write can be recovered. Versioning is not locking; it is recovery.

Loading diagram...

force-unlock is a last resort

Usage: terraform force-unlock [options] LOCK_ID .

Official behavior:

It manually unlocks state when automatic unlocking failed.

It does not modify infrastructure .

The lock's behavior depends on the backend.

Local state files cannot be unlocked by another process.

Be very careful . Unlocking a lock someone else still holds creates multiple writers.

Use it only to unlock your own lock after automatic unlock failed (crash, killed CI job, lost session).

The command requires the unique lock ID Terraform printed when unlock failed. That ID is a nonce so you target the correct lock.

-force skips the confirmation prompt. # Terraform printed this ID after a crashed apply left the lock behind.

terraform force-unlock 9a3c1e2b-77d4-4c10-9f08-0b6a0d2e4a11

Do not script force-unlock at the start of every pipeline. Do not invent a lock ID. If another apply is still running, wait or use -lock-timeout on your command.

Scenario: the Friday lock

Ava starts terraform apply against an S3 backend with use_lockfile = true . The runner is SIGKILLed mid-apply. The .tflock object remains. Ben's apply fails immediately with a lock error and a lock ID. Ben must not force-unlock while Ava might still be running. After they confirm the runner is dead, Ava (or an owner of that run) force-unlocks that ID. The next apply is a new writer. If the team had omitted use_lockfile and omitted dynamodb_table , both applies would have written the same key with no lock at all.

004 traps for objective 6b

Locking is automatic for write operations, not a resource you declare in HCL.

A failed lock aborts ; Terraform does not "apply anyway and warn."

-lock=false exists and is discouraged.

S3 on Terraform 1.12 does not lock until you set use_lockfile (or the deprecated dynamodb_table ).

force-unlock needs the printed LOCK_ID , does not change objects, and is not for someone else's live run.

Local locks do not span two checkouts.