TrademarkTrademark
Features
Documentation

How to Update State in a Remote Backend

Pull, edit, and push Terraform or OpenTofu state stored in a remote backend without corrupting it, plus the serial error that trips almost everyone up the first time.
Ryan FeeJuly 16, 2026
Key takeaways
  • Before you can update state remotely, confirm your configuration actually points at a remote or cloud backend (not the default local one) and that `terraform init` has connected to it; a local backend has no separate remote copy to pull, push, or conflict with.
  • There are several distinct ways to update state once a remote backend is confirmed: manual pull/edit/push, `terraform state mv`, `terraform state rm`, `terraform import`, a declarative `moved` block, `apply -refresh-only`, and, on Scalr, a UI upload or one-click rollback with no CLI at all.
  • The manual push fails if the local file's serial isn't strictly higher than the remote's, or if the lineage doesn't match; both checks exist to stop you from overwriting newer state with a stale copy.
  • "Remote" and "cloud" refer to specific Terraform config blocks, `backend "remote"` and the `cloud {}` block, not to object-storage backends like S3, GCS, and azurerm, which are their own backend types and use neither block. Scalr's standard setup uses `backend "remote"`, but also accepts the `cloud` block for tag-based workspace selection; HCP Terraform's current configurations use `cloud` as the default.

Updating state in a remote backend comes down to two questions: is your backend actually remote, and which of the several ways to update state fits what you're trying to fix? First confirm the backend, then pick the method.

Confirm you have a remote or cloud backend configured

None of this works against the default local backend, there's no separate remote copy to pull, push, or conflict with, so confirm the backend first. "Remote" and "cloud" here mean two specific Terraform config blocks, not a general description of any non-local backend.

The remote backend type (Scalr)

Scalr's standard setup, and older HCP Terraform configurations, use backend "remote":

terraform {
  backend "remote" {
    hostname     = "<my-account>.scalr.io"
    organization = "<ID of environment>"
    workspaces {
      name = "<workspace-name>"
    }
  }
}

The cloud block (HCP Terraform, and Scalr for tag-based workspaces)

Current HCP Terraform configurations use a dedicated cloud block instead of backend "remote". Scalr accepts it too, specifically when you want to select a workspace by tags rather than by name:

terraform {
  cloud {
    hostname     = "<my-account>.scalr.io"
    organization = "<ID of environment>"
    workspaces {
      tags = ["<your-tags>"]
    }
  }
}

After terraform init, Scalr looks for workspaces in that environment matching the tags: one match auto-selects it, several prompt you to choose, none prompts you to create a new one. This is the one place Scalr's config diverges from plain backend "remote", which only supports selecting a workspace by name, not by tags.

Where object-storage backends fit

S3, GCS, and azurerm are also remote in the everyday sense of the word, your state isn't on your laptop, but they're their own backend types (backend "s3", backend "gcs", backend "azurerm"), not the remote or cloud block. They also work differently once you get to Step 2: they treat the state file as a single object in a bucket, with no workspace concept or run history layered on top, and locking is bolted on separately (a DynamoDB table for S3, native locking for GCS and azurerm) rather than being part of the same system that tracks versions. The remote and cloud blocks add a workspace-level API on top of that same interface, with retrievable version history and, on Scalr, permission checks (state-versions:read to view, separate access to write) before anything lands.

Verify the connection

Run terraform init after adding a backend or cloud block. Terraform reports what it connected to, and if you had local state already, it offers to copy it up. Confirm the connection actually landed with:

terraform state pull

Against a remote or cloud backend this downloads the real state from Scalr, HCP Terraform, S3, or wherever it lives. Against the local backend (no block, or a backend "local" block) it just reads whatever terraform.tfstate file happens to be sitting next to your code, which is the tell that you're not actually remote yet.

Choose how you want to update the state

Seven ways, roughly in order of how often you'll reach for each.

Move or rename a resource: terraform state mv

Use this when a resource's address changed, moving into a module, a for_each, or a rename, but the underlying infrastructure didn't:

terraform state mv aws_instance.web module.compute.aws_instance.web

Runs directly against the remote backend. No pull/push needed.

Remove a resource without destroying it: terraform state rm

Use this when infrastructure was deleted outside Terraform (someone removed it in the console) or you're deliberately handing a resource off to a different state file:

terraform state rm aws_instance.old_bastion

This only edits state. If the resource still exists and is still in your code, the next apply will try to recreate it.

Bring existing infrastructure under management: terraform import

Use this when a resource exists in the cloud and in your code, but Terraform has never tracked it:

terraform import aws_instance.web i-0d13d5591a41e18ef

Works the same against a remote backend as a local one; no special steps. The Terraform import guide covers import {} blocks and bulk imports in more depth.

Refactor without touching state directly: a moved block

Use this for the same rename/relocate case as state mv, but as code that ships through a normal pull request instead of a one-off command someone has to remember to run:

moved {
  from = aws_instance.web
  to   = module.compute.aws_instance.web
}

Apply it and Terraform updates state to match. Nothing to push manually.

Sync state to what's actually deployed: terraform apply -refresh-only

Use this when you suspect drift, someone changed a tag or a setting directly in the cloud console, and want state to reflect reality before you decide whether to fix it in code or accept it:

terraform apply -refresh-only

Shows you what changed and asks for confirmation before writing anything.

Manual edit: pull, change, push

Use this only when nothing above covers the case, a corrupted attribute, merging state from two sources by hand:

terraform state pull > terraform.tfstate
# edit terraform.tfstate
terraform state push terraform.tfstate

This is the one that fails the first time almost everyone tries it. Every state file carries a serial number that increments on every write and a lineage UUID identifying its history; Terraform refuses the push if your file's serial isn't higher than what's already stored remotely, or if the lineage doesn't match. The error looks like this:

Failed to write state: cannot overwrite existing state with serial 1 with a different state that has the same serial.

Fix it by bumping serial in your local copy to at least one higher than the remote value before pushing again. If the remote shows "serial": 3, set yours to 4:

{
  "version": 4,
  "terraform_version": "1.5.7",
  "serial": 4,
  ...
}

Both checks can be bypassed with terraform state push -force, which skips the lineage and serial comparisons entirely. Don't, unless you're certain the state you're pushing should win; it silently overwrites whatever's remote, including changes you didn't know about.

Before you push a manual edit: back it up first (terraform state pull > backup.tfstate, redundant on Scalr since every push already versions, but the only safety net at all on an object-storage backend without bucket versioning turned on); pause anything else that could write to the same state, a scheduled run, a teammate's apply, a CI pipeline; and run terraform plan right after the push lands, a clean plan means your edit and reality still agree, a plan that wants to create or destroy things you didn't touch means it doesn't.

No CLI at all: UI upload or rollback (Scalr)

On a remote-operations backend you're not limited to the CLI. Scalr's workspace State tab has an "Upload state file" action, and every past version has a one-click rollback: pick an older version, roll back, and nothing about your actual infrastructure changes until the next run detects a difference. Object-storage backends have no equivalent; recovering a bad push to an S3 bucket means whatever you set up ahead of time, bucket versioning, most commonly, restored through the AWS console or CLI rather than a purpose-built UI.

One more thing: locks

A stuck lock isn't an update, but it'll block one. Clear it with:

terraform force-unlock LOCK_ID

using the lock ID from the error message. It only clears the lock and never touches infrastructure, but confirm nothing is actually running first, force-unlocking a genuinely active plan or apply can let two writes race. What the lock physically is depends on the backend: a DynamoDB row for S3, a native lease for GCS and azurerm, a workspace-level flag for Scalr and HCP Terraform. If lock errors are a recurring problem rather than a one-off, the state lock errors guide covers root causes and prevention in more depth than fits here.

For the broader command reference beyond what's above, taint/untaint included, see the guide to updating Terraform state. If what actually broke is an empty or corrupted state file rather than a normal edit, that's a different recovery path, covered in the empty state file recovery guide.

Frequently asked questions

How do I check that I'm actually using a remote or cloud backend?

Run `terraform init`. If your configuration has a `backend "remote"` block or a `cloud` block, Terraform connects to it and reports what it found. Scalr's standard setup uses `backend "remote"` (it also accepts `cloud` for tag-based workspace selection); current HCP Terraform configurations default to `cloud`. `terraform state pull` is a second confirmation: against either it downloads the state from that service; against the default local backend it just reads the `terraform.tfstate` file sitting next to your code, since there's nothing remote to pull from. Object-storage backends like S3, GCS, and azurerm are checked the same way but use their own backend type, not `remote` or `cloud`.

What are the different ways to update Terraform state in a remote backend?

Seven, in practice: manual pull/edit/push for anything the other commands don't cover, `terraform state mv` to rename or relocate a resource, `terraform state rm` to remove a reference without destroying the resource, `terraform import` to bring an existing resource under management, a declarative `moved` block for refactors that ship through normal code review, `terraform apply -refresh-only` to sync state to what's actually deployed, and, on a remote-operations backend like Scalr, a UI upload or one-click rollback that skips the CLI entirely.

Why does terraform state push fail with a serial error?

Terraform rejects the push if the destination's serial is higher than or equal to the one you're pushing, and separately if the lineage (a UUID identifying the state's history) doesn't match. Both are safety checks: a stale serial means someone else's newer state would get overwritten. Fix it by setting the local file's serial to at least one higher than the remote value before pushing again.

Can I edit Terraform state without using the CLI?

On a remote-operations backend, sometimes. Scalr lets you upload a state file directly from the workspace's State tab, and separately offers one-click rollback to any previous state version without touching a terminal. Raw object-storage backends like S3 have no such UI: you're editing the object directly or restoring a prior version through the cloud provider's own console.

Is it safe to manually edit a Terraform state file?

It works, but it's a last resort. State encodes resource IDs, dependency graphs, and provider metadata that Terraform expects to be internally consistent; a manual edit that breaks that consistency won't surface until the next plan or apply, sometimes as a cryptic error, sometimes as a plan that wants to destroy and recreate everything. Back up the state before you touch it, and prefer `terraform state mv`, `rm`, and `import` over hand-editing the JSON when they cover your case.

Does state push work differently for HCP Terraform or Scalr than for S3?

The command is the same, but what's behind it differs. S3, GCS, and azurerm treat the state as an object in your bucket; there's no workspace concept or run history layered on top. The `remote` backend type and the `cloud` block, whether you're pointed at Scalr or HCP Terraform, add a workspace-level API, versioned state history in that workspace, and (in Scalr's case) permission checks (state-versions:read to view, write access to push) before the file lands. That's also why `terraform state push` documents a `-ignore-remote-version` flag specific to the cloud/remote backend types, to bypass a Terraform-version compatibility check that plain object-storage backends don't perform.
About the author
Ryan Feedirector of platform engineering at Scalr
Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.