TerraformX · State Operations: Read, Move, Remove, ImportProduction Terraform
replace-provider: Switching Provider Namespaces
What you'll learn
- Use terraform state replace-provider to migrate state between provider sources (e.g. hashicorp/aws to mycorp/aws)
- Plan a provider migration with verification gates
- Recognise when replace-provider is wrong (different provider version with different schema, true fork)
- Recover from a failed replace-provider with the backup file
Prerequisites
None — start here.
Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13
Provider sources in Terraform have a namespace (hashicorp,
mycorp, cloudflare) and a name (aws, google,
cloudflare). The combination is the source address:
hashicorp/aws, mycorp/aws, cloudflare/cloudflare. When the
source changes — a community provider moves to a vendor namespace,
the team adopts an in-house fork, a deprecated provider is
replaced — the state records the old source. Terraform needs to
be told the source has changed; otherwise the next plan will
either recreate every resource or fail with “provider not found”.
What replace-provider does
terraform state replace-provider rewrites the provider field
in every resource instance in state from the old source to the
new one. The real-world resources are untouched. The next plan
sees the new provider source, finds state that matches, and
proposes no changes.
Before:
provider = "provider[\"registry.terraform.io/hashicorp/aws\"]"
Real world: i-0a1b2c3d4e5f6a7b8 (running)
After:
provider = "provider[\"registry.terraform.io/mycorp/aws\"]"
Real world: i-0a1b2c3d4e5f6a7b8 (still running, now managed by mycorp/aws)
The state now points at the new provider. The real-world resource is unchanged. The next plan will succeed if the new provider exposes the same resource types and attributes as the old one.
When replace-provider is right
- HashiCorp adopts a community provider. Example: the AWS
provider was originally
terraform-providers/awsand later moved tohashicorp/aws. State from older runs references the old source; the new run needs the new source. - The team adopts a vendor-maintained fork. Example: a cloud
vendor publishes their own provider under their namespace
(
cloudflare/cloudflareinstead ofterraform-providers/cloudflare). - The team adopts an in-house fork. Example: a security patch
is needed; the team forks the provider under
mycorp/awsand switches.
When replace-provider is wrong
- Schema divergence. If the new provider exposes different attributes than the old one, the plan will show a massive diff. The right answer is to evaluate the new provider against the old one, not to migrate wholesale.
- Different cloud. A provider for AWS cannot be replaced with
a provider for GCP — the resource types are completely
different.
replace-provideris not a cross-cloud migration tool. - Major version upgrade with breaking changes. A 4.x → 5.x provider upgrade with renamed attributes should be planned attribute by attribute, not bulk-replaced.
The procedure
# 1. Pull state to a backup
terraform state pull > /tmp/state-before-replace.json
# 2. Confirm the old source is in use
terraform state pull | jq -r '.resources[].provider' | sort -u
# provider["registry.terraform.io/hashicorp/aws"]
# 3. Add the new provider to the configuration
# terraform {
# required_providers {
# mycorp-aws = {
# source = "mycorp/aws"
# version = "~> 1.0"
# }
# }
# }
# provider "mycorp-aws" { alias = "..." }
# 4. terraform init
terraform init -upgrade
# Terraform downloads the new provider and locks the version.
# 5. Run replace-provider
terraform state replace-provider -backup-file=/tmp/replace-backup.tfstate \
'registry.terraform.io/hashicorp/aws' \
'registry.terraform.io/mycorp/aws'
# Terraform rewrites every resource instance to the new provider.
# 6. Plan; expect no changes (if the new provider is drop-in)
terraform plan
# No changes. Your infrastructure matches the configuration.
# (If the new provider differs: the plan shows the diff. Investigate.)
# 7. Apply
terraform apply
A real example: adopting a vendor fork
A team is migrating from hashicorp/aws to aws/aws (the vendor
namespace for AWS itself, used for AWS-specific features not in
the HashiCorp provider).
terraform {
required_providers {
aws = {
source = "aws/aws"
version = "~> 1.0"
}
}
}
terraform init -upgrade
terraform state replace-provider -backup-file=/tmp/replace.tfstate \
'registry.terraform.io/hashicorp/aws' \
'registry.terraform.io/aws/aws'
terraform plan
# Expect no changes if the resource types and attributes align.
# If the plan shows changes, the providers differ; investigate.
The backup file is the recovery artifact if the replace-provider causes unexpected diffs.
Validation
READ-ONLY
# Confirm the provider source has changed in state
terraform state pull | jq -r '.resources[].provider' | sort -u
# provider["registry.terraform.io/mycorp/aws"]
# Confirm the plan is empty (or contains only expected diffs)
terraform plan
# No changes. Your infrastructure matches the configuration.
# (or a reviewed diff)
# Confirm the dependency lock file reflects the new provider
cat .terraform.lock.hcl | grep 'mycorp/aws'
Production failure modes
Symptom: replace-provider errors with “provider not configured”.
Cause: the new provider is not in required_providers or init
has not been run. Add the provider to the configuration block and
run terraform init -upgrade.
Symptom: replace-provider succeeds but the plan shows a massive diff. Cause: the new provider is not drop-in; attributes or defaults differ. Investigate the diff; consider rolling back via the backup file before applying.
Symptom: replace-provider succeeds but the apply fails with “provider returned error”. Cause: the new provider cannot manage the resource type (it was removed or renamed). Investigate the provider’s documentation; do not retry the apply blindly.
Symptom: lock acquire failed during replace-provider. Cause: another apply is in progress. Wait or break the lock with team agreement.
Symptom: serial advanced but the plan still references the old
provider. Cause: replace-provider did not run; the error was
silent. Investigate with state pull.
Recovery
- Stop. Do not apply.
- Restore state from the backup file (S3 versioning, Terraform Cloud state versions).
- Verify with
terraform plan— expect the original provider source. - Investigate the divergence between the old and new providers before retrying.
What comes next
The next lesson covers moved blocks — the declarative
counterpart to state mv for refactoring resource addresses in
code, reviewable in pull requests.
Verification
- You can describe when
state replace-provideris the right tool (community provider adoption, vendor migration, in-house fork). - You can run the procedure: backup, init, replace-provider, plan, apply.
- You can recognise the symptoms of a non-drop-in provider fork (massive plan diff after replace).
- You can recover from a failed replace-provider with the backup file.
Knowledge check · 7 questions
Q1. What does `terraform state replace-provider` do?
Q2. What is the right pre-flight before running state replace-provider?
Q3. state replace-provider will surface schema divergence between the old and new providers as a plan diff.
Q4. Which scenario is a valid use of state replace-provider?
Q5. Which of the following are pre-flight steps for a state replace-provider? (Select all that apply.)
Q6. After replace-provider, the plan shows a massive diff across hundreds of resources. What does this mean?
Q7. A team is migrating from hashicorp/aws to mycorp/aws because the vendor fork has a security patch. After running replace-provider, the plan is empty. What is the right next step?
Passing score: 75%. Answers are checked in this browser.