The Error Scenario
You run terraform plan โ or even just terraform validate โ and Terraform stops cold with this message:
Error: Duplicate provider "aws" configuration
A module may have only one "default" (non-aliased) provider configuration per provider type.
Nothing gets planned. Nothing gets applied. The config you thought was fine just refused to load.
This usually happens in one of three situations:
- You copy-pasted a provider block from another file into a module that already had one
- You split your Terraform config across multiple
.tffiles and defined the same provider in two of them - You added a second AWS region config without realizing a default provider block already existed in
main.tf
Why Terraform Throws This Error
Terraform allows only one default provider block per provider type within a single module. A default provider is any provider block that does not have an alias argument set. When Terraform finds two blocks like this in the same module โ regardless of which .tf file they're in โ it can't determine which one to use as the default, so it errors out rather than guessing.
Here is the minimal example that reproduces the error:
# main.tf
provider "aws" {
region = "us-east-1"
}
# providers.tf โ same module, different file
provider "aws" {
region = "ap-southeast-1"
}
Terraform merges all .tf files in a directory into one logical module. Both blocks above become default providers for aws. That's the conflict.
Quick Fix: Add an Alias to the Second Provider
If you actually need two different AWS configurations (two regions, two accounts, two sets of credentials), the fix is to keep one block as the default and give the other an alias.
# providers.tf
provider "aws" {
region = "us-east-1" # default provider โ no alias
}
provider "aws" {
alias = "ap"
region = "ap-southeast-1"
}
Any resource that needs the second configuration references it explicitly with the provider argument:
resource "aws_s3_bucket" "asia_logs" {
provider = aws.ap
bucket = "my-asia-logs-bucket"
}
Resources without a provider argument automatically use the default (non-aliased) block. You only need to specify it when you want the aliased one.
Permanent Fix: Remove the Duplicate
If you only need one AWS configuration, the real fix is to delete the duplicate block entirely. Start by finding every occurrence:
grep -rn 'provider "aws"' .
Sample output might look like:
./main.tf:1:provider "aws" {
./providers.tf:1:provider "aws" {
Open both files, decide which one has the correct settings, and delete the other. Then consolidate everything into a single, authoritative provider block โ the conventional place is providers.tf or versions.tf:
# providers.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
profile = "my-profile"
}
Special Case: Child Modules
If you're calling a child module and want it to use a specific provider configuration, pass the provider from the root using a providers map. The child module should not define its own provider "aws" {} block โ it should only declare what it requires.
# Root module โ calls child and passes a provider
module "networking" {
source = "./modules/networking"
providers = {
aws = aws.ap # pass the aliased provider explicitly
}
}
# modules/networking/versions.tf โ declare requirement only, no full provider block
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
Adding a provider "aws" { region = ... } block inside the child module would create a second default provider from Terraform's perspective โ the same error, just harder to spot because it's in a subdirectory.
Verification Steps
Run these three commands in order after making your changes:
# 1. Validate configuration syntax and structure
terraform validate
# 2. Inspect the provider dependency tree
terraform providers
# 3. Confirm the plan runs cleanly
terraform plan
terraform validate should output:
Success! The configuration is valid.
terraform providers is especially useful for diagnosing where a provider is being pulled in from. It shows the full tree including child modules, so if a duplicate is hiding in a nested module, you'll see it here.
Prevention Tips
The single most effective habit: keep all provider and version declarations in one file (providers.tf or versions.tf), and never put a provider block anywhere else. When the whole team knows providers live in one file, accidental duplication drops to near zero.
Add a validation step to your CI pipeline so this error gets caught before it reaches anyone's local machine:
# .github/workflows/terraform.yml (excerpt)
- name: Validate Terraform
run: |
terraform init -backend=false
terraform validate
When you're working with configurations that involve variable files mixing YAML and JSON formats โ which is common in larger Terraform projects โ I find ToolCraft's YAML โ JSON Converter handy for quickly verifying the structure of .tfvars or variable definition files before feeding them into Terraform. It runs entirely in the browser, nothing gets uploaded.

