keepcalculator

06 Numbers and Powers

Terraform Remote State: Backends, Locking & Security

Card No. 06·38755 · Updated · Standard formula, shown below

Terraform remote state stores your infrastructure’s state file in a shared location instead of on your local machine. You enter the backend configuration (e.g., an S3 bucket, Azure Storage, or GCS bucket), the state file is uploaded there after every apply, and Terraform uses it to plan and update resources. This article explains how remote state backends work, why state locking matters, and how to secure the state file itself.

Why Remote State Exists

By default, Terraform writes a terraform.tfstate file in your working directory. That file is the single source of truth for what resources Terraform manages. If you work alone and never share the file, local state is fine. But as soon as a team touches the same infrastructure, problems appear:

  • Two people run terraform apply at the same time; the second overwrites the first’s changes.
  • Someone deletes or edits the state file by accident, and Terraform loses track of real resources.
  • You need to see the current state from a CI/CD pipeline or a colleague’s laptop.

Remote state solves these by storing the file in a central location that everyone can access. It also enables state locking, which prevents concurrent modifications, and it keeps sensitive values (like passwords and IP addresses) out of local files that might get committed to Git.

How Backends Work

A backend is a pluggable storage destination for state. Terraform supports two types: standard backends (S3, AzureRM, GCS, etc.) and enhanced backends (local and remote). The remote backend works with Terraform Cloud or Terraform Enterprise and adds features like policy checks and run history. Standard backends are the most common for self-managed setups.

When you configure a backend, Terraform automatically:

  • Uploads the state file after every successful apply.
  • Downloads the latest state before every plan and apply.
  • Creates a lock (if the backend supports it) during operations that modify state.

Example S3 backend configuration:

terraform {
  backend "s3" {
    bucket = "my-terraform-state"
    key    = "prod/network/terraform.tfstate"
    region = "us-east-1"
  }
}

You must run terraform init after changing the backend block. Terraform will ask if you want to copy existing state to the new backend; answer yes if you want to migrate.

State Locking Explained

State locking prevents two Terraform runs from modifying the same state file at the same time. Without it, two people could run apply simultaneously; each would read the same state, plan different changes, and the last write would silently discard the other’s updates. That leads to drift and even resource deletion.

Most backends support locking natively:

  • S3 uses DynamoDB for locking (you must create a DynamoDB table and specify its name in the backend config).
  • AzureRM uses a blob storage lease.
  • GCS uses a bucket’s object versioning and a lock file.
  • HTTP backends can support locking if the server implements the API.

When a lock is acquired, Terraform writes a lock ID. If another run tries to acquire it, Terraform waits (up to a timeout) and then errors. You can force-unlock with terraform force-unlock <LOCK_ID> if a run crashed and left a stale lock, but use that sparingly—it can cause corruption if another run is actually in progress.

Security: Protecting the State File

The state file contains every resource’s attributes, including sensitive data like database passwords, API keys, and instance IDs. Treat it as a secret. Here are the essential security measures:

  • Encrypt at rest. Enable server-side encryption on your storage (S3 SSE-S3 or SSE-KMS, Azure Storage encryption, GCS default encryption).
  • Encrypt in transit. Terraform uses HTTPS for all backend API calls, so this is usually automatic.
  • Restrict access with IAM. Only allow specific users or roles to read/write the state bucket. Use least-privilege policies—most team members only need read access for planning; only a few need write access for applying.
  • Enable versioning. S3 and GCS versioning let you recover from accidental deletion or corruption.
  • Enable MFA delete. On S3, this adds an extra layer to prevent accidental state deletion.
  • Use a dedicated bucket. Don’t mix state with other data. Give it a clear name like company-terraform-state.

A common misconception: storing state in a private bucket is enough. It’s not—if an IAM user has broad S3 access, they can read the state and extract secrets. Always combine bucket policies with tight IAM roles and consider using a tool like Vault to manage dynamic secrets instead of storing them in state.

Common Backend Issues and Solutions

Even with a good setup, you’ll hit issues. Here are the most frequent ones and how to fix them:

  • Lock acquisition timeout. Another run is holding the lock. Check if a CI job is stuck; if not, force-unlock with the lock ID from the error message.
  • State file corruption. If the file is malformed, restore from a versioned backup (S3 versioning, GCS object versions).
  • Backend configuration drift. Someone changes the backend block without running terraform init; Terraform will complain about mismatch. Run terraform init -reconfigure to fix.
  • Permission denied. The IAM role/user doesn’t have s3:GetObject or s3:PutObject on the bucket. Check your policy.
  • DynamoDB table missing. For S3 backend, you must create the DynamoDB table before locking works. The table needs a primary key named LockID (type String).

Choosing a Backend

Your choice depends on where your cloud infrastructure lives and what features you need. Here’s a quick comparison:

  • S3 (AWS): Best if you’re all-in on AWS. Supports locking via DynamoDB, versioning, and cross-region replication.
  • AzureRM (Azure): Uses Azure Storage with blob leases. Good for Azure-centric teams.
  • GCS (Google Cloud): Simple, supports locking via object versioning. Good for GCP users.
  • HTTP: For custom or on-premises setups; locking depends on server implementation. Not recommended unless you have a reason.
  • Remote (Terraform Cloud): Includes locking, history, and policy controls, but requires a Terraform Cloud account and has cost implications.

For most teams, using the native backend of your primary cloud provider is the simplest and most reliable choice. If you use multiple clouds, consider a dedicated backend like an S3 bucket that all teams can access (with appropriate IAM).

Costs and Operational Overhead

Remote state backends are cheap. S3 charges for storage (pennies per GB per month) and PUT/GET requests (fractions of a cent per thousand). DynamoDB for locking costs a few dollars per month for the read/write capacity you need. Azure and GCS are similarly low-cost. The real cost is operational: you must maintain IAM policies, monitor bucket access, and ensure the backend is available. If the backend goes down, you can’t run terraform plan or apply, so consider adding replication or a failover strategy if uptime is critical.

Alternatives to Remote State

If you can’t use a cloud backend, there are alternatives:

  • Local state with version control: Store the state file in Git, but this is risky—merge conflicts and accidental changes are common. Only viable for solo projects.
  • Consul backend: HashiCorp Consul can store state and supports locking. Useful if you already run Consul.
  • etcd: Similar to Consul, but less common.
  • Terraform Cloud: The managed alternative, which handles locking and history for you.

In practice, remote state on a cloud object store is the standard. It’s reliable, cheap, and well-documented.

Frequently Asked Questions

Can I use remote state without locking?

Yes, but it’s dangerous. Some backends (like plain HTTP without lock support) don’t provide locking. Without it, concurrent runs can corrupt state. If your backend doesn’t support locking, you must ensure only one person runs Terraform at a time, which is impractical for teams.

Why does Terraform store secrets in state?

Terraform stores the full attribute values of every resource, including those marked sensitive, because it needs them to plan future changes. For example, if you create a database with a password, Terraform must know that password to compare it during the next plan. The best practice is to generate secrets dynamically (e.g., using a random_password) and rotate them regularly, plus restrict access to the state file.

How do I migrate from local to remote state?

Add the backend block to your configuration, then run terraform init. Terraform will detect the change and offer to copy the existing local state to the new backend. Confirm, and it will upload the state. After that, always run terraform init in fresh clones to download the remote state.

What happens if I delete the state file?

If you delete the state file, Terraform loses track of all resources it manages. The next plan will show everything as “to be created,� and apply will attempt to recreate resources, which may fail if they already exist. That’s why versioning and backups are essential.

Can I use multiple backends for different environments?

Yes. Use a separate key (path) in the same bucket for each environment, or use separate buckets. For example, dev/terraform.tfstate and prod/terraform.tfstate. This isolates state and allows different access permissions per environment.

Is remote state secure enough for production?

Yes, if you follow the security measures above: enable encryption, restrict IAM, enable versioning, and use locking. The main risk is overly permissive IAM policies that let anyone read the state. A well-configured remote backend is more secure than local state because it’s centralized and auditable.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →