Safeguard
Supply Chain•

What provider version constraint should a shared Terraform module use?

Shared Terraform modules should set only a minimum provider version with >=, while root modules pin with ~>. Why the split exists and how to review it.

Safeguard Security Team
4 min read

A shared Terraform module should constrain providers with a minimum only, such as >= 5.0, while the root module that calls it sets both a lower and an upper bound with ~>. The module declares what it needs. The root module decides what actually runs.

What HashiCorp recommends

Two HashiCorp pages say the same thing. The module providers page says that for shared modules you should constrain only the minimum required provider version using a >= constraint, set to the earliest version that has the features the module uses, so callers can select newer versions.

The version constraints page adds the other half. Reusable modules should constrain only their minimum Terraform and provider versions. Root modules should use ~> to set both a lower and an upper bound for each provider they depend on.

Why the split makes sense

Think of constraints as negotiation. Every module in a configuration states a range it can work with, and Terraform must find one provider version that satisfies all of them. If a shared module sets a tight upper bound, it can make two modules impossible to use together, because their ranges no longer overlap.

The root module is different. It is the place where the whole configuration is assembled and tested, so it can afford to be strict. The lock file then records the exact selection. See what the Terraform lock file protects.

The operators in one table

OperatorMeaningGood home
>= 5.0Any version from 5.0 upwardShared module
~> 5.1Allows 5.x from 5.1 up, not 6.0Root module
~> 5.1.4Allows 5.1.5 and 5.1.10, not 5.2.0Root module, tighter
= 5.1.4 or 5.1.4Exactly one versionRare, breaks easily
!= 5.1.3Excludes a known bad versionEither, with a comment

For ~>, HashiCorp explains that only the rightmost version component may increment.

Pre-release caution

Terraform does not match pre-release versions on >, >=, <, <= or ~> operators. A pre-release is only selected when named exactly. So a >= floor will not pull in a beta by accident.

How to set the floor honestly

  1. List the provider features the module uses, such as a specific argument.
  2. Find the release that introduced each one in the provider's changelog.
  3. Take the latest of those releases as the floor.
  4. Record the reason in a comment next to the constraint.
  5. Revisit the floor when the module starts to use a newer argument.

A floor that is too low causes confusing errors for callers on old versions. A floor that is too high forces needless upgrades.

Reviewing a module change

When a pull request touches required_providers, ask:

  • Did the floor rise? Callers on older providers will fail at init, so release notes should say so.
  • Was an upper bound added? That conflicts with the guidance above and needs a strong reason.
  • Is the source address unchanged? A changed namespace is a different provider, and the pull request should explain it. See required_providers source addresses.

Where Safeguard fits

Safeguard's SBOM and dependency scanning covers the dependencies your repositories declare, and a gate can require review when manifests change. Provider version choices still need the human judgement described here.

Quick check

Should a shared module ever pin an exact provider version?

Rarely. An exact pin leaves callers no room to upgrade and can conflict with other modules. Pin exact versions in the root module through the lock file instead.

Does the root module need an upper bound?

HashiCorp recommends ~> in root modules so each provider has both a lower and an upper bound.

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.