Paperlive
HOME / BLOG / TERRAFORM STATE EXPLAINED: REMOTE BACKENDS, LOCKING & BEST PRACTICES (2026)

Terraform State Explained: Remote Backends, Locking & Best Practices (2026)

Terraform State Explained: Remote Backends, Locking & Best Practices (2026)

Short answer: Terraform state is a JSON file (terraform.tfstate) that maps the resources in your code to the real resources in your cloud account. Terraform reads it on every plan and apply to work out what to create, change or destroy. For any team, store state in a remote backend such as Amazon S3, Azure Blob Storage, Google Cloud Storage or HCP Terraform, turn on state locking, encrypt it and never edit it by hand.

What Is Terraform State?

When you run terraform apply, Terraform creates real infrastructure: a VPC, an EC2 instance, a database. It then writes down what it created, including IDs, attributes and dependencies, in a state file. That file is Terraform's memory.

Without state, Terraform would have no way to know that aws_instance.web in your code is the instance i-0abc123 running in your account. Every plan would try to create everything again.

If you are new to the tool, start with our guide on what Terraform is and how Infrastructure as Code works.

Why Does Terraform Need a State File?

State does four jobs:

  • Mapping code to reality. It links each resource block to a real cloud object ID.
  • Tracking metadata. It stores dependency order, so Terraform knows to delete a server before the subnet it lives in.
  • Performance. It caches attribute values, so large projects do not have to query every resource from the cloud API on every run.
  • Detecting drift. On each plan, Terraform refreshes state and compares it with your code and the real world, so you can spot manual changes made in the console.
Ready to become a DevOps engineer?
Industry-certified DevOps training online · 100% job-opportunity guarantee
Get Curriculum →

What Is Inside a terraform.tfstate File?

A state file is plain JSON. A trimmed example looks like this:

{
  "version": 4,
  "terraform_version": "1.9.8",
  "serial": 12,
  "lineage": "3f2a9c1e-...",
  "resources": [
    {
      "mode": "managed",
      "type": "aws_s3_bucket",
      "name": "logs",
      "instances": [
        { "attributes": { "id": "acme-prod-logs", "arn": "arn:aws:s3:::acme-prod-logs" } }
      ]
    }
  ]
}
  • serial goes up by one every time state changes.
  • lineage is a unique ID that stops you from accidentally overwriting one project's state with another's.
  • resources holds every managed object and all of its attributes.

Important: state stores attribute values in plain text, including database passwords, private keys and tokens if a resource returns them. Treat the state file as a secret.

Local State vs Remote State

By default Terraform writes state to a local file in your working directory. That is fine for learning, and risky for anything else.

  • Where it lives: local state sits on your laptop; remote state lives in S3, Azure Blob, GCS or HCP Terraform.
  • Team sharing: local state cannot be shared safely; remote state can.
  • State locking: local state locks only on one machine; remote backends lock across every user and CI job.
  • Encryption at rest: local depends on your disk; remote uses KMS or provider-managed keys.
  • Versioning and recovery: local needs manual backups; remote gets bucket versioning and state history.
  • Best for: local for tutorials and quick tests; remote for any shared or production project.

Rule of thumb: if more than one person or one CI pipeline touches the code, use remote state.

What Is a Terraform Backend?

A backend decides where Terraform stores state and how it locks it. You configure it inside the terraform block. Popular backends include:

  • s3 for AWS
  • azurerm for Azure Blob Storage
  • gcs for Google Cloud Storage
  • HCP Terraform (the cloud block), which also gives you a UI, run history and policies. See Terraform Cloud vs Terraform OSS.
  • kubernetes, pg (PostgreSQL), consul and http for special cases

How to Set Up a Remote Backend on AWS S3 (2026 Way)

Since Terraform 1.10, the S3 backend supports native state locking using a lock file in the same bucket. You no longer need a DynamoDB table. In Terraform 1.11 and later, the dynamodb_table argument is deprecated, so new projects should use use_lockfile.

Step 1: Create a secure state bucket

Create the bucket once, in a separate bootstrap project, with versioning, encryption and public access blocked:

resource "aws_s3_bucket" "tf_state" {
  bucket = "acme-terraform-state-prod"
}

resource "aws_s3_bucket_versioning" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  versioning_configuration { status = "Enabled" }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "tf_state" {
  bucket = aws_s3_bucket.tf_state.id
  rule {
    apply_server_side_encryption_by_default { sse_algorithm = "aws:kms" }
  }
}

resource "aws_s3_bucket_public_access_block" "tf_state" {
  bucket                  = aws_s3_bucket.tf_state.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

Step 2: Point your project at the backend

terraform {
  required_version = ">= 1.10"

  backend "s3" {
    bucket       = "acme-terraform-state-prod"
    key          = "network/prod/terraform.tfstate"
    region       = "ap-south-1"
    encrypt      = true
    use_lockfile = true
  }
}

Step 3: Initialise and migrate

terraform init -migrate-state

Terraform will copy your existing local state into S3. Confirm with yes, then delete the local terraform.tfstate and its .backup file from your machine.

Migrating from DynamoDB locking? Add use_lockfile = true while keeping dynamodb_table for one release cycle, run terraform init -reconfigure, confirm locks work, then remove the DynamoDB argument and table.

What Is Terraform State Locking?

State locking stops two people, or two pipeline runs, from changing the same state at the same time. When apply starts, Terraform takes a lock. Anyone else who runs a write operation sees an error like "Error acquiring the state lock" and has to wait.

Without locking, two concurrent applies can both read the same old state and write conflicting results. That is how teams end up with orphaned resources and corrupted state.

If a run crashes and leaves a stale lock, you can release it, but only after you are sure no other run is active:

terraform force-unlock <LOCK_ID>

Essential Terraform State Commands

  • terraform state list lists every resource tracked in state.
  • terraform state show <address> shows all attributes of one resource.
  • terraform state mv <from> <to> renames or moves a resource address without destroying it.
  • terraform state rm <address> stops tracking a resource but leaves it running in the cloud.
  • terraform state pull downloads the current remote state to stdout.
  • terraform import brings an existing cloud resource under Terraform management.
  • terraform plan -refresh-only shows drift between state and the real world without changing anything.

Prefer config blocks over CLI surgery

Modern Terraform lets you make most state changes in code, where they are reviewed in a pull request:

  • moved blocks (1.1+) replace terraform state mv when you rename resources or refactor into reusable modules.
  • import blocks (1.5+) replace the terraform import command and can even generate config for you.
  • removed blocks (1.7+) replace terraform state rm.
moved {
  from = aws_instance.web
  to   = module.app.aws_instance.web
}

import {
  to = aws_s3_bucket.legacy
  id = "acme-legacy-bucket"
}

How to Structure State: One File or Many?

Putting your whole company in a single state file is a common mistake. Plans get slow, the blast radius of a bad apply is huge and everyone fights for the same lock.

Instead, split state by environment and component:

acme-terraform-state-prod/
  network/prod/terraform.tfstate
  database/prod/terraform.tfstate
  app/prod/terraform.tfstate
acme-terraform-state-dev/
  network/dev/terraform.tfstate
  app/dev/terraform.tfstate

Use separate buckets, or at least separate keys and IAM permissions, for production and non-production. A developer who can change dev should not be able to read prod state.

Workspaces vs separate directories

Terraform CLI workspaces let one configuration keep several state files (for example default, staging, prod). They are handy for short-lived copies of the same stack. For long-lived environments with different settings and access rules, most teams prefer separate directories or separate root modules, because it is harder to apply to prod by mistake.

Sharing outputs between stacks

Use the terraform_remote_state data source, or better, publish values to a parameter store such as AWS SSM Parameter Store, so one stack can read another stack's outputs without full access to its state.

Terraform State Best Practices

  1. Always use a remote backend for shared or production code.
  2. Enable state locking (use_lockfile on S3; built in on Azure Blob, GCS and HCP Terraform).
  3. Encrypt state at rest with KMS or provider-managed keys, and enforce HTTPS.
  4. Turn on bucket versioning so you can roll back a corrupted or deleted state file.
  5. Lock down access with IAM. Only CI and a small admin group should read or write prod state.
  6. Never commit state to Git. Add *.tfstate, *.tfstate.* and .terraform/ to .gitignore.
  7. Never edit state by hand. Use moved, import and removed blocks, or the terraform state commands.
  8. Keep secrets out of state where you can. Use ephemeral resources and write-only arguments in newer Terraform versions, or fetch secrets at runtime from a vault. Read our guide to secrets management in DevOps.
  9. Split state by environment and component to cut blast radius and plan time.
  10. Run applies from CI/CD, not laptops. One pipeline with one identity makes locking and auditing simple. See CI/CD pipeline security best practices.
  11. Check for drift on a schedule with terraform plan -refresh-only or plan -detailed-exitcode, and alert when it finds changes.
  12. Pin your Terraform and provider versions so a newer CLI does not upgrade the state format unexpectedly.

Common Terraform State Problems and How to Fix Them

"Error acquiring the state lock"

Another run is active, or a crashed run left a lock. Check your CI for running jobs first. Only if nothing is running, use terraform force-unlock <LOCK_ID>.

Resource exists in the cloud but not in state

Someone created it manually, or it was removed from state. Bring it back with an import block, then run plan and confirm Terraform shows no changes.

Terraform wants to destroy and recreate after a rename

You renamed a resource or moved it into a module. Add a moved block from the old address to the new one and the plan will show a move instead of a replace.

State file was deleted or corrupted

Restore the previous version from bucket versioning (or HCP Terraform state history), then run terraform plan to check it matches reality. This is why versioning is not optional.

Drift from manual console changes

Run terraform plan -refresh-only to see what changed. Either update your code to match, or run apply to put the infrastructure back to what the code says.

Terraform State vs OpenTofu State

OpenTofu, the open-source fork of Terraform, uses a compatible state format and the same backends, so migration is usually simple. One notable extra: OpenTofu supports client-side state encryption, so state is encrypted before it ever leaves your machine. Compare the two in our Terraform vs OpenTofu guide.

Key Takeaways

  • Terraform state is the record that links your code to real infrastructure.
  • Use a remote backend with locking, encryption and versioning for every shared project.
  • On AWS, use the S3 backend with use_lockfile = true; DynamoDB locking is deprecated.
  • Change state through moved, import and removed blocks, not by hand.
  • Split state by environment and component, and run applies from CI.

Conclusion

State is the part of Terraform that beginners ignore and experienced engineers obsess over, for good reason. Get it right on day one with a locked, encrypted, versioned remote backend and a sensible split by environment, and most of the scary Terraform stories never happen to you.

Preparing for an interview? State management comes up in almost every round. Practise with our Terraform interview questions, and if you want hands-on practice with Terraform, AWS and CI/CD on real projects, explore the DevOps courses at PaperLive Learning.

Get the curriculum

DevOps Course Online

100% secure · no spam · callback in 30 min

Recommended Course

Online DevOps course
BESTSELLER
AWSAzureGCPGen AI
Online DevOps Course
Job-Ready Program

Learn from Microsoft-certified experts with real projects, internship certification and dedicated placement support to help you land your next DevOps role.

6 Months
Duration
5–45 LPA
Opportunity range
300+ Hrs
Live sessions
IIT Patna
Certification
Next batch closing soon — limited seats
Live classes · 23 Nov
View full curriculum
© 2025 PAPERLIVE, All rights reserved

Disclaimer: PaperLive Learning offers skill-based training and professional upskilling courses only. Our programs are not postgraduate (PG) programs, diploma courses, or formal academic qualifications and do not award degrees or diplomas.

We strive to ensure that the information and pricing displayed on our website are accurate. However, course fees, content, duration, and promotional offers may change without prior notice. Applicable pricing at the time of enrollment will be communicated clearly.

Career outcomes depend on individual skills, effort, experience, performance, and market conditions. Placement statistics, salary figures, and success stories are for informational purposes only and do not guarantee similar results.

References to third-party platforms, certifications, and tools are for educational purposes only and do not imply endorsement or affiliation unless explicitly stated.