The situation
I was cleaning up a Terraform repo โ consolidating providers into a single root module, removing a duplicate provider block from a child module. The config looked fine. Then I ran terraform plan and got this:
Error: Provider configuration not present
To work with aws_s3_bucket.my_bucket its original provider configuration
at module.storage.provider["registry.terraform.io/hashicorp/aws"] is
required, but it has been removed. This occurs when a provider
configuration is removed while objects created by that provider
still exist in the state.
The state still holds resources tied to a provider path that no longer exists in code. Without that config, Terraform has no idea which credentials or region to use โ so it refuses to do anything.
Why this happens
Every resource in terraform.tfstate is linked to a specific provider config address โ not just the provider type, but the exact path including module scope and alias. Remove that path, and the state reference breaks.
Common triggers:
- Removing a
providerblock from a module - Deleting or renaming a module that had its own provider config
- Dropping a provider alias (e.g.,
provider "aws" { alias = "us-east" }) - Moving resources between modules without updating state to match
Terraform won't guess. If the state points to module.storage.provider["registry.terraform.io/hashicorp/aws"] and that address doesn't exist in your .tf files, it errors immediately โ before evaluating anything else.
Debug: find what's orphaned
Start by listing everything in state:
terraform state list
Then inspect a specific resource:
terraform state show module.storage.aws_s3_bucket.my_bucket
Check the provider field in the output. It'll show something like module.storage.provider["registry.terraform.io/hashicorp/aws"]. That path must exist in your config โ or the resource needs re-linking to a provider that does exist.
For a quick scan of all provider references in state:
cat terraform.tfstate | grep '"provider"'
This is faster than inspecting resources one by one, especially when a whole module was removed and you're looking at 20+ orphaned resources.
Fix option 1 โ Temporarily restore the provider block
Quickest way to unblock yourself: add the missing provider config back exactly as it was.
# In module/storage/main.tf โ restore what was removed
provider "aws" {
region = var.aws_region
}
Run terraform init && terraform plan. If the plan shows no unexpected changes, you're unblocked. From here you can do a proper migration instead of scrambling.
This isn't the permanent fix โ it's a safe rollback that buys you time to do the migration correctly.
Fix option 2 โ Move the resource in state to a new provider
Refactoring provider config to the root module? Use terraform state replace-provider. It re-links resources to the new provider address without touching real infrastructure:
terraform state replace-provider \
"module.storage.registry.terraform.io/hashicorp/aws" \
"registry.terraform.io/hashicorp/aws"
The resource now points to the root-level AWS provider. Nothing in AWS changes โ only the state file is updated.
Verify the fix:
terraform state show module.storage.aws_s3_bucket.my_bucket
# provider field should now show the root-level provider path
Most engineers don't know this command exists until they hit this exact error. It's the cleanest option when you're reorganizing provider locations.
Fix option 3 โ Move the resource address in state
If you moved the resource itself (not just its provider), you need to update the full state address too:
terraform state mv \
module.storage.aws_s3_bucket.my_bucket \
aws_s3_bucket.my_bucket
This tells Terraform: the same real S3 bucket is now tracked at this new address under the root-level provider.
# Confirm the move worked
terraform state list | grep s3
terraform plan # should show no changes if config matches reality
Fix option 4 โ Remove the resource from state
Sometimes the right answer is to just stop tracking it. If the resource is already gone, or you plan to delete it manually, remove it from state:
terraform state rm module.storage.aws_s3_bucket.my_bucket
Terraform forgets the resource. It won't attempt to destroy it โ it simply stops managing it. Useful when decommissioning infrastructure that's already been cleaned up outside of Terraform.
Dealing with many affected resources
Remove a whole module and now 30 resources are orphaned? Do it in bulk:
# See everything under the old module path
terraform state list | grep 'module.storage'
# Move each resource to the root level
terraform state mv module.storage.aws_s3_bucket.my_bucket aws_s3_bucket.my_bucket
terraform state mv module.storage.aws_dynamodb_table.locks aws_dynamodb_table.locks
# ... repeat for each resource
After the moves, update your .tf files to match the new addresses. Then run terraform plan โ if it shows "No changes", the state and config are back in sync.
Back up state before you start
Before touching any provider config during a refactor, take a snapshot:
cp terraform.tfstate terraform.tfstate.backup-$(date +%Y%m%d)
Remote state? Pull a local copy first:
terraform state pull > state-backup.json
The state file is plain JSON. If you want to grep through provider references or explore the structure without installing extra tools, the YAML โ JSON Converter at ToolCraft works well โ paste the JSON in and browse it in the browser. Nothing gets uploaded, which matters when your state file contains IAM credentials or resource ARNs.
Verification
After applying any fix above, run through this checklist:
# Reinitialize providers
terraform init
# Confirm no unexpected changes
terraform plan
# Spot-check a specific resource is still mapped to real infra
terraform state show <resource_address>
"No changes" in the plan output means the state and provider config are properly aligned โ no infrastructure was modified in the process.
Lessons learned
- Migrate state before removing provider blocks. Always update state first. Remove the config second. Doing it backwards is exactly how you land in this error.
terraform state replace-provideris the underdog command. It's purpose-built for provider refactors and far cleaner than the alternatives โ but almost nobody knows it exists until they hit this exact situation.- Module-scoped providers create tight coupling. Resources in a module with its own
providerblock get locked to that path in state. Pass providers explicitly via theprovidersargument instead โ future refactors become far less painful. - State, not
.tffiles, is the source of truth at plan time. Terraform matches state addresses to config addresses. If they diverge, nothing runs. Fix the state first.

