Safeguard
Security Guides•

Should a shared Terraform module contain provider blocks?

No. A reusable Terraform module should not define provider blocks. Why it breaks for_each and count, and how to pass providers in from the caller.

Safeguard Security Team
4 min read

No. A reusable Terraform module should declare which providers it needs but should not contain provider blocks. Provider configurations belong in the root module, where the caller decides credentials, regions and accounts. A provider block inside a module also blocks common features.

What the rule says

HashiCorp's provider documentation for modules says a reusable module must not contain any provider blocks, and that provider configurations can be defined only in a root module. Each module must declare its own provider requirements, using a required_providers block with the source address, but only the configurations are inherited, not the requirements.

There is also a practical penalty. Modules with their own provider blocks are incompatible with for_each, count and depends_on, and Terraform raises an error if you combine them. To fix a legacy module, remove every provider block and have callers pass providers in.

Why it matters for security review

A provider block is where credentials, assumed roles, regions and endpoints are chosen. If a shared module sets those itself, then:

  • Reviewers of the calling stack cannot see which account the module touches.
  • A module update can quietly change the target account or role.
  • Teams cannot apply least privilege per caller, because the module carries its own.

Keeping provider configuration in the root module puts that decision where the stack owner reviews it.

Passing providers in

When a module needs more than one configuration, declare aliases with configuration_aliases inside required_providers, for example aws.alternate. The caller then passes matching configurations through the providers argument of the module block. Resources inside the module pick an alias with their provider argument.

A short outline:

# inside the module
terraform {
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      version               = ">= 5.0"
      configuration_aliases = [aws.alternate]
    }
  }
}
# in the root module
module "replica" {
  source = "./modules/replica"
  providers = {
    aws           = aws
    aws.alternate = aws.west
  }
}

The version floor in the example is illustrative. Pick the earliest version that has features your module uses. See the provider constraint post.

What goes wrong in practice

The usual story is a module written early, when one account and one region were all anyone used. Someone put a provider block in it for convenience. Years later a second team wants to call it in a loop for several environments, and Terraform refuses because of the for_each restriction. The fix then touches every caller at once, which is far harder than getting it right at the start.

The lesson is that a module's interface should be settled before many teams depend on it. Provider setup is part of that interface.

A review checklist

  1. Search the module for provider " blocks. There should be none.
  2. Confirm required_providers names a source for every provider used.
  3. Check every alias is declared with configuration_aliases.
  4. Confirm examples show the providers argument.
  5. Make sure the module still works with for_each and count.

Where Safeguard fits

Safeguard policies and gates can hold a pull request until the right reviewers approve it, and its compliance and evidence automation can keep a record of that approval. It does not review your provider setup for you, so keep a human step for that.

Quick check

Does a module need required_providers if it has no provider blocks?

Yes. HashiCorp says each module must declare its own provider requirements, because requirements are not inherited, only configurations.

Can a root module define providers?

Yes. Provider configurations can be defined only in a root module.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.