Terraform records which backend it last initialised with in .terraform/terraform.tfstate. When the backend block differs, it stops and asks, because the two possible intentions have very different consequences.
Migrating copies the existing state to the new location. Reconfiguring forgets the old backend and starts using the new one as-is.
Pick the wrong one and you either duplicate state or lose track of it.
See what changed
python3 -c "import json;d=json.load(open('.terraform/terraform.tfstate'));print(json.dumps(d.get('backend',{}).get('config',{}),indent=2))"
{
"bucket": "acme-tfstate",
"key": "prod/terraform.tfstate",
"region": "eu-west-1"
}
Compare against your backend block. Any difference triggers this, including ones that do not change where state lives:
| Changed | Usually want |
|---|---|
bucket or key | -migrate-state |
| Local to remote, or the reverse | -migrate-state |
role_arn, profile, credentials | -reconfigure |
use_lockfile, dynamodb_table | -reconfigure |
region, with the bucket moved | -migrate-state |
region, correcting a typo | -reconfigure |
Migrating
terraform init -migrate-state
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend
to the newly configured "s3" backend. No existing state was found in the
newly configured "s3" backend. Do you want to copy this state?
Enter "yes" to copy and "no" to start with an empty state.
Read that prompt properly. Answering no gives you an empty state while your infrastructure still exists, and the next apply tries to create everything again.
Back up first, always:
terraform state pull > backup-$(date +%Y%m%d-%H%M).tfstate
Afterwards, verify:
terraform state list | wc -l
terraform plan # should show no changes
A plan proposing to create everything means the migration did not carry the state. Restore the backup and try again:
terraform state push backup-20260921-1430.tfstate
Reconfiguring
terraform init -reconfigure
No copying, no prompt. It discards the recorded backend and initialises the new one.
Correct when:
- Only credentials, a profile or a role changed
- The target backend already holds the right state, such as a colleague's machine initialising against a shared backend for the first time
- You are switching workspaces or environments that have their own state
Wrong when the state has not been copied yet, because you end up pointing at an empty backend with real infrastructure behind it.
The safe default in CI
terraform init -reconfigure -lockfile=readonly
CI runners start clean, so there is no .terraform directory and nothing to migrate. -reconfigure makes the behaviour deterministic and stops a cached workspace from producing a surprise prompt. -lockfile=readonly fails the build if the provider lock file would need to change.
Never use -migrate-state in automation. It is interactive by nature, and the one time it does something unexpected you want a human present.
Partial configuration
Hardcoding a bucket name means a different file per environment. Leave the backend block empty and supply values at init:
terraform {
backend "s3" {}
}
terraform init -reconfigure \
-backend-config="bucket=acme-tfstate" \
-backend-config="key=prod/terraform.tfstate" \
-backend-config="region=eu-west-1"
Or with a file per environment:
# backends/prod.hcl
bucket = "acme-tfstate"
key = "prod/terraform.tfstate"
region = "eu-west-1"
terraform init -reconfigure -backend-config=backends/prod.hcl
Note that backend blocks cannot use variables or expressions. They are evaluated before anything else, so key = "${var.env}/terraform.tfstate" is rejected. Partial configuration exists precisely because of that limitation.
Switching environments in one directory
terraform init -reconfigure -backend-config=backends/staging.hcl
terraform plan
The -reconfigure is essential here. Without it Terraform offers to migrate production state into the staging backend, and a hurried yes merges two environments.
Workspaces avoid the whole issue for environments that share configuration:
terraform workspace select prod
They share one backend with separate state paths, so there is no re-init between environments.
A checklist
- Read the recorded backend from
.terraform/terraform.tfstate. - Diff it against your backend block to see which key changed.
- State needs to move →
-migrate-state, afterterraform state pull > backup. - Read the migration prompt. Answering "no" gives you an empty state.
- Verify with
terraform state listand a plan showing no changes. - Only credentials or locking changed →
-reconfigure. - In CI, always
-reconfigure, never-migrate-state. - Use partial configuration with
-backend-configfor per-environment values.
Frequently Asked Questions
What is the difference between -migrate-state and -reconfigure?
-migrate-state copies the existing state from the old backend into the new one and prompts you to confirm. -reconfigure discards Terraform's record of the previous backend and initialises the new one without copying anything. Use migrate when state genuinely needs to move, such as going from local to S3 or changing the key. Use reconfigure when the state is already in the right place and only something like credentials or a role changed.
Why does Terraform ask about this when I only changed the IAM role?
Because it compares the entire backend configuration against what it recorded at the last init, and any difference triggers the check. It has no way to know that role_arn affects how it authenticates rather than where state lives. This is the clearest case for -reconfigure: the state has not moved, so there is nothing to migrate and copying would be wrong.
What happens if I answer "no" to the migration prompt?
Terraform initialises the new backend with an empty state while your real infrastructure carries on existing. The next plan then proposes creating everything from scratch, and applying it either fails on resources that already exist or creates duplicates. Always run terraform state pull > backup.tfstate before a migration, and after it verify with terraform state list and a plan that shows no changes.
Why can I not use variables in a backend block?
Because the backend is initialised before Terraform evaluates variables, locals or anything else in the configuration, so there is nothing available to interpolate. That is why partial configuration exists: leave the block empty and supply the values at init time with -backend-config flags or a .hcl file per environment. It keeps a single set of Terraform files working across environments without hardcoding a bucket and key.
Should I use -reconfigure in CI?
Yes, always, and never -migrate-state. CI runners start from a clean checkout with no .terraform directory, so there is no previous backend to migrate from and nothing to copy. -reconfigure makes the behaviour deterministic even if a cached workspace is reused. -migrate-state is interactive by design and belongs in a session where a person can read the prompt and decide, which is exactly what a pipeline cannot do.