Don't force unlock Terraform state. Do this instead.
When you see a locked Terraform state, your first instinct might be to force-unlock it and move on. That instinct will eventually corrupt your state file, create duplicate resources, or cause two processes to fight over the same infrastructure. The lock is not the problem. The lock is protecting you from the problem.
Terraform's state locking exists to prevent two processes from modifying the same state simultaneously. When you run terraform apply, Terraform acquires an exclusive lock on the state file through whatever backend you've configured (DynamoDB for S3, blob leases for Azure, lock files for GCS), performs its operations, then releases the lock. Simple, effective, and absolutely necessary for preventing race conditions that would leave your infrastructure in an undefined state.
But locks get stuck. A crashed CI job leaves a lock in DynamoDB, a canceled apply doesn't clean up properly, a network timeout orphans a lock file in S3. You're blocked, deployments are stalled, and terraform force-unlock is sitting right there in the documentation, promising to fix everything. The command exists for a reason, but using it wrong will hurt you worse than waiting.
How state locking actually works
Terraform's locking mechanism is backend-specific, but the pattern is the same across all implementations. Before modifying state, Terraform attempts to acquire a lock by writing a lock record to some shared store. If the write succeeds (meaning no lock exists), Terraform proceeds. If the write fails (because another process holds the lock), Terraform either waits (if you specified -lock-timeout) or fails immediately with an error message containing the lock ID, who holds it, and when it was acquired.
With S3 backends using the classic DynamoDB approach, Terraform writes an item to a DynamoDB table with a key identifying the state file. The lock record contains metadata (the lock ID, who acquired it, when, what operation). DynamoDB's conditional writes ensure only one process can create the lock item. Once the operation finishes, Terraform deletes the item, releasing the lock. If you enable DynamoDB TTL on the lock table's timestamp field, stale locks will eventually expire automatically, though you need to set the TTL duration longer than any legitimate apply run or you'll create different problems.
Terraform 1.5 introduced S3-native locking using use_lockfile = true, which creates a temporary .tflock object in the S3 bucket itself rather than relying on DynamoDB. The lock is acquired through S3's atomic PUT with an "if-generation-match: 0" precondition (meaning the lock file must not already exist). This eliminates the DynamoDB dependency entirely, and HashiCorp has indicated the DynamoDB approach may eventually be deprecated in favor of this simpler method. The behavior is identical from Terraform's perspective, just different plumbing underneath.
Azure backends use blob leases on the state file itself. When Terraform locks state, it acquires a lease on the blob containing .tfstate. Azure guarantees only one lease holder at a time. If another process tries to access the blob, it fails to acquire the lease and Terraform reports the lock error. The lease is released when the operation completes. No separate lock table, no extra infrastructure, just Azure Storage's native locking primitive.
GCS backends create a separate .tflock file in the same bucket as the state. The lock acquisition uses a generation precondition (x-goog-if-generation-match: 0) to ensure the lock file is only created if it doesn't exist, making the operation atomic. If the lock file already exists, the precondition fails and Terraform knows someone else holds the lock. The lock file is deleted when the operation finishes.
Local backends use OS file locking with a temporary .tfstate.lock.info file created next to the state file. This contains the lock ID and metadata. While this technically works for preventing concurrent local processes on the same machine, it's worthless for distributed teams. Local state is not recommended for anything beyond individual experimentation because it doesn't support true remote coordination.
The risks are not theoretical
Using terraform force-unlock tells Terraform to manually remove a lock on the state, overriding the safety mechanism entirely. This should only happen when you are absolutely certain the lock is stale, meaning the process that created it has crashed, been killed, or otherwise terminated without releasing the lock. If you force-unlock while another Terraform process is still running, you've created the exact race condition that state locking was designed to prevent.
Two Terraform processes modifying the same state simultaneously will corrupt the state file or create resource conflicts. The state file is a JSON document containing a serialized mapping of resources and their metadata. If two Terraform instances write to it concurrently, one might truncate or override sections from the other. You'll end up with a state that doesn't parse correctly, has missing or duplicate resources, or no longer matches the actual infrastructure deployed in your cloud provider. Using force-unlock at the wrong time can lead to data corruption or conflicts if multiple processes are modifying state simultaneously.
Even if the state file doesn't outright corrupt, you can end up with infrastructure drift. Imagine a terraform apply creating ten resources, getting halfway through, then crashing. Some resources exist in the cloud, but the state file may not have been updated to record them because the write was interrupted. The lock remains in place. If you force-unlock and run another apply, Terraform will see those resources as new (since state doesn't record them) and might attempt to create them again, fail with conflicts, or leave you with duplicate infrastructure that Terraform no longer tracks properly. Whenever a Terraform operation fails or is aborted mid-execution, the state may not reflect the real infrastructure changes that happened. Removing the lock and continuing blindly exacerbates this mismatch.
You also lose the opportunity to investigate. Terraform's lock contains metadata showing who locked it, when, and for what operation. This information appears in the DynamoDB item, the local .tfstate.lock.info file, or the lock error output Terraform prints. If you immediately force-unlock, you destroy this context. Was it a colleague's run still in progress? A CI pipeline that's actually still applying changes in the background? A stuck lock from yesterday that's genuinely safe to remove? Without checking the lock metadata, you're guessing, and guessing wrong means race conditions.
On a team level, force-unlocking without coordination catches your teammates off guard. If someone is running Terraform in a terminal and you force-unlock, their process might continue unaware that you've started another apply. Now two people are fighting over the state file, each thinking they have exclusive access. The locked state usually signals "someone is working on it, please wait." Removing that signal prematurely breaks team communication and creates coordination failures.
What to actually do instead
When you encounter a locked state, resist the urge to immediately force-unlock. First, confirm that no Terraform process is actually running against that state. Check your CI/CD pipelines (Jenkins, GitHub Actions, GitLab CI, CircleCI, whatever you're using). Ask your teammates if anyone has an apply in progress. Verify that the process which previously held the lock has genuinely crashed or been killed. Only proceed to force-unlock if you're completely certain the lock is orphaned.
Inspect the lock metadata before taking action. For DynamoDB locks, go to the AWS console or use the CLI to examine the lock table item. The Info field contains JSON with details like Who (the user or process that acquired the lock), Created (timestamp), and the Terraform version. This tells you how old the lock is and who owns it. If it's a few seconds old and owned by a teammate, ping them to see if they have a run in progress. If it's hours old and the user confirms they're not running anything, it's likely stale. For local backends, open the .tfstate.lock.info file to see the same metadata. Identifying stale locks this way prevents you from erroneously unlocking a valid lock.
Always backup the state file before manual intervention. With remote backends like S3, download the .tfstate and .tfstate.backup files from the bucket. If you have versioning enabled on the bucket (which you absolutely should), you have historical versions to fall back on. Having a backup means if force-unlocking leads to corruption or unexpected behavior, you can restore the last known-good state and try a different approach. This is a safety net that costs nothing and could save your infrastructure.
Communicate before unlocking. If you determine a lock is stuck, announce your intent to the team. A quick message in Slack, Teams, or whatever communication tool you use can prevent two people from both trying to fix the problem simultaneously. In production environments, consider a runbook procedure like "Terraform state X is locked since last night's failure, I'm going to unlock it now after backing up state, please ensure no one else is applying." Clear communication prevents overlapping operations and keeps everyone aware of what's happening to shared infrastructure.
Use lock timeouts to handle transient contention. By default, Terraform fails immediately if it can't acquire a lock. Setting -lock-timeout=5m (or 10m, 15m, whatever duration makes sense for your apply times) tells Terraform to wait for the lock to be released before erroring out. This won't help with truly stuck locks, but it reduces noise from two applies started a few seconds apart where one would naturally finish and release the lock if you just waited. If your pipeline consistently hits the timeout, that indicates a genuinely stuck lock or concurrent runs that need coordination, not just transient contention.
Implement automated lock expiry where possible. For DynamoDB-based locking, configure a TTL attribute on the lock table so items expire after a certain duration (one hour, one day, whatever is longer than your longest legitimate apply but short enough to not leave stale locks indefinitely). Terraform doesn't natively set the TTL, but you configure the DynamoDB table itself with TTL enabled on the timestamp field. This provides an automatic safety valve so orphaned locks eventually disappear without manual intervention. Be careful with the TTL duration because if it's too short, you'll expire locks on legitimate long-running applies, which would be disastrous.
Add lock monitoring and alerting. In production environments, treat Terraform locks as observable metrics. Set up a CloudWatch metric filter or a periodic Lambda that detects stale locks in your DynamoDB table (any item older than X minutes). Send alerts to your team via SNS, Slack, PagerDuty, whatever. This lets you respond to stuck locks proactively rather than discovering them when urgent changes are blocked. Additionally, monitor for long-running locks (a lock held for more than 30 minutes might indicate a hung apply that needs investigation). Automated monitoring flags problems before they escalate.
Use CI/CD concurrency controls to prevent contention in the first place. GitLab CI has a resource_group feature to ensure only one pipeline uses a given resource at a time. GitHub Actions has a concurrency key to prevent parallel runs on the same environment. Jenkins has similar mutual exclusion plugins. Configure these features for jobs that modify the same Terraform state so you're less likely to encounter two jobs contending for the lock simultaneously. Terraform's state lock will still protect you if something slips through, but CI-level locking is an additional safeguard that serializes infrastructure deployments before they even reach Terraform.
Handle abnormal terminations gracefully. If you run Terraform in CI containers, capture interrupt or failure signals to kill Terraform gracefully. Terraform will catch a SIGINT (Ctrl+C) and attempt to unlock the state before exiting. A hard SIGKILL might not allow cleanup. Some teams implement wrapper scripts or pipeline logic to detect aborted jobs and run terraform force-unlock automatically with the known lock ID, though this has to be done carefully to avoid racing. At minimum, document the procedure in your runbooks (if you cancel a Terraform job, immediately check the state lock and unlock if needed).
Consider whether splitting state makes sense for your architecture. If many team members frequently clash on one monolithic state, breaking it into separate states or workspaces might reduce contention. For example, network infrastructure in one state, application infrastructure in another. They can be managed independently, so a lock on one doesn't block changes to the other. However, don't split state just for the sake of it. Only do this if the resources are genuinely independent and the split makes architectural sense. Splitting introduces complexity (cross-state references via terraform_remote_state, coordination of changes that span both states), so the tradeoff needs to be worth it.
Evaluate whether a managed Terraform service solves the problem better than manual lock management. Terraform Cloud and Terraform Enterprise queue runs automatically, handle locks transparently, and eliminate the need for manual force-unlocks. Third-party platforms like Terrateam, add additional coordination layers (PR-level locking, run queuing, automatic cleanup on completion) that prevent the scenarios where force-unlock becomes necessary. If you're frequently reaching for force-unlock, it's a signal that your workflow needs more automation, not just better lock hygiene.
If terraform force-unlock fails or isn't possible (maybe the CLI can't reach the backend), the ultimate fallback is manually removing the lock via backend tools. For DynamoDB, that means deleting the lock item in the table using the AWS console or CLI. For GCS, delete the .tflock object. For Azure, you can break the blob lease if necessary. This is essentially what force-unlock does under the hood, but you're doing it manually. Use this only as a last resort and double-check you're deleting the correct item for the correct workspace or environment. Manual intervention bypasses all of Terraform's safety checks.
Force-unlock is a break-glass tool
Terraform's documentation emphasizes that force-unlock should be used only when absolutely necessary and with extreme caution. The command exists for genuine emergencies where a lock is provably stale and needs removal, not as a routine part of your workflow. If you find yourself running force-unlock regularly, something is wrong with your process. Maybe CI jobs are crashing frequently (fix the root cause, don't just unlock and retry). Maybe your team lacks coordination on who runs Terraform when (establish a policy, use Terrateam, implement CI-level controls). Maybe you're not using backend features like TTL or lock timeouts that would prevent stale locks in the first place.
The safest strategy is prevention through architecture and process. Most teams institute rules like "never run terraform apply locally on production, only via CI" to prevent ad-hoc processes that are hard to track. If a lock does get stuck, follow a checklist (confirm no active process, inspect lock metadata, communicate with the team, backup state, then unlock and immediately verify state consistency). This disciplined approach avoids the race conditions, drift, and corruption that come from hasty force-unlocks.
State locking is not the enemy. State locking is the safeguard that prevents two processes from simultaneously rewriting the JSON file that represents your entire infrastructure. When you encounter a lock, it's Terraform doing its job. The question is whether the lock is still valid (in which case you wait or coordinate) or stale (in which case you investigate, backup, communicate, then carefully remove it). Force-unlock is the tool for the second scenario, not the first, and the consequences of using it in the first scenario can be catastrophic.
If Terraform state locking feels like an impediment to velocity, that's because it is. It trades speed for correctness, serialization for safety. That's the architecture Terraform chose, and it's worth understanding why. File-based state with global locks doesn't scale gracefully to large teams making concurrent changes across independent resources, which is part of why alternative approaches (resource-level locking, event-sourced state, deterministic concurrency models) exist. But within Terraform's model, the lock is not optional and bypassing it is dangerous. Respect the lock, investigate stuck locks thoroughly, and use force-unlock only when you've confirmed it's safe.
The real fix is better coordination
Stuck locks are a symptom of coordination failures. A CI job that crashes without cleanup. A teammate who canceled an apply without checking if the lock released. A long-running operation that hit a network timeout and orphaned the lock. These are process problems, not Terraform problems. Force-unlock treats the symptom, but fixing the root cause requires better tooling and discipline.
Automated workflows that handle locks correctly (Terrateam's PR-level locking) eliminate most scenarios where you'd need to force-unlock. CI concurrency controls prevent multiple jobs from contending in the first place. Lock monitoring alerts you to problems before they block deployments. DynamoDB TTL or lock timeouts provide automatic recovery from transient failures. These solutions cost effort to implement, but they're far cheaper than recovering from state corruption caused by an ill-timed force-unlock.
When you reach for terraform force-unlock, stop and ask why you're in this situation. Is there a CI job that needs fixing? A timeout that's too aggressive? A team member who needs training on how Terraform locking works? A missing runbook for handling stuck locks properly? Answering those questions will improve your infrastructure operations far more than learning to force-unlock faster. The goal is to make force-unlock so rare that when you do need it, you can take the time to do it carefully with full awareness of the risks.