Skip to content

4.06 Terraform Provider Version Constraints

Overview

By default, terraform init downloads the latest available version of each provider plugin a configuration needs. Version constraints let you pin, exclude, or bound which provider versions Terraform is allowed to install instead.

Abstract

Provider functionality can change meaningfully between versions, and a configuration written against one version may not behave the same on another. The required_providers block inside a terraform block lets you specify an exact version or a range of acceptable versions for each provider, making terraform init predictable and repeatable.

Why It Matters in Production

Letting terraform init silently pull the latest provider version is risky — a provider upgrade can introduce breaking changes that cause a previously working configuration to fail, behave differently, or produce unexpected plans. Pinning or constraining provider versions keeps environments consistent across team members, CI pipelines, and time, and gives you control over when and how a provider upgrade happens.

Key Concepts

Concept Description
terraform block Top-level block for configuring Terraform itself, including required provider versions
required_providers Block inside terraform { } listing each provider's source address and version constraint
source The provider's registry address, e.g. hashicorp/local
version A version constraint string controlling which provider versions are acceptable
= (exact) Installs exactly the specified version
!= (not equal) Excludes the specified version
<, <=, >, >= Bounds versions below or above a given value
~> (pessimistic) Allows the specified version or any higher version within the same major/minor segment

Common Use Cases

  • Pinning an exact provider version so a configuration's behavior stays consistent across terraform init runs, regardless of when or where it's run.
  • Excluding a specific provider version known to have a bug or breaking change, using !=.
  • Allowing only patch-level upgrades (bug fixes) while blocking minor or major version changes, using the pessimistic constraint operator ~>.
  • Combining multiple constraints to define an acceptable version range, e.g. allowing anything newer than one version but older than another.

Example Configuration or Commands

Default behavior — latest version

Without any version constraint, terraform init downloads the latest provider release:

resource "local_file" "pet" {
  filename = "/root/pet.txt"
  content  = "We love pets!"
}

The Terraform Registry page for a provider shows its latest version and a dropdown of all prior releases — for the local provider, the latest is 2.0.0, with 1.4.0, 1.3.0, 1.2.2, and others available below it.

Pinning an exact version

The Registry's "Use Provider" button generates the snippet needed to require a specific version:

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "1.4.0"
    }
  }
}

resource "local_file" "pet" {
  filename = "/root/pet.txt"
  content  = "We love pets!"
}
terraform init
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/local versions matching "1.4.0"...
- Installing hashicorp/local v1.4.0...
- Installed hashicorp/local v1.4.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running
"terraform plan" to see any changes that are required for your infrastructure. All
Terraform commands should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Not equal — excluding a version

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "!= 2.0.0"
    }
  }
}

Terraform will not install version 2.0.0; it falls back to the next available version, 1.4.0.

Less than / greater than

version = "< 1.4.0"
version = "> 1.1.0"

The first constraint allows any version older than 1.4.0; the second allows any version newer than 1.1.0.

Combining constraints into a range

terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "> 1.2.0, < 2.0.0, != 1.4.0"
    }
  }
}

This requires a version greater than 1.2.0, less than 2.0.0, and explicitly not 1.4.0. Given the available releases, Terraform resolves this to version 1.3.0.

Pessimistic constraint operator (~>)

version = "~> 1.2"
terraform init
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 1.2.0"...
- Installing hashicorp/local v1.2.2...
- Installed hashicorp/local v1.2.2 (signed by HashiCorp)

Terraform has been successfully initialized!

~> 1.2 allows version 1.2 or any later version within the 1.x line (1.3, 1.4, ... up to but not including 2.0). Since the highest available release in that range is 1.4.0, that's what gets installed.

Using a more specific value narrows the allowed range further:

version = "~> 1.2.0"

~> 1.2.0 only allows 1.2.0 through 1.2.x (patch-level increments) — not 1.3.0 or higher. With 1.2.2 being the highest patch release available, Terraform installs 1.2.2.

Best Practices

Best Practices

  • Pin exact provider versions in production configurations to keep behavior consistent across team members and CI runs.
  • Use the pessimistic constraint operator (~>) when you want to allow safe patch updates automatically but block breaking minor/major changes.
  • Check the Terraform Registry's provider documentation and changelog before widening a version constraint or upgrading.
  • Commit the .terraform.lock.hcl dependency lock file to version control so every terraform init resolves to the same exact provider build.

Security Best Practices

Security

  • Always verify that terraform init reports a provider as signed (e.g. "signed by HashiCorp") before trusting it in a pipeline.
  • Avoid leaving provider versions completely unconstrained in shared or production configurations — an unexpected upstream release could introduce vulnerabilities or behavior changes without review.
  • Review provider release notes for security advisories before relaxing a version constraint to allow newer releases.

Do and Don't

✅ Do ❌ Don't
Pin or constrain provider versions in shared configurations Leave provider versions unconstrained and rely on whatever is "latest" at init time
Use ~> to allow safe patch-level upgrades Use ~> without understanding which version segment it locks
Test provider upgrades in a non-production environment first Upgrade provider versions directly in production without review
Commit the lock file so installs are reproducible Assume terraform init always resolves to the same version without a lock file

Common Mistakes

Common Mistakes

  • Omitting the required_providers block entirely and being surprised when terraform init pulls a newer, incompatible provider version later.
  • Misreading ~> 1.2 as locking to exactly 1.2.x, when it actually allows any 1.x version greater than or equal to 1.2.
  • Combining contradictory constraints (e.g. a lower bound higher than an upper bound) and not noticing until terraform init fails to resolve a version.

Troubleshooting

# Re-run after changing version constraints
terraform init

# Force Terraform to re-evaluate and install provider versions
terraform init -upgrade

# Confirm which provider version is currently in use
terraform providers

Real-World Examples

Platform Team — Pinning to Avoid a Breaking Provider Upgrade

Scenario: A team's CI pipeline ran terraform init without any version constraints on a cloud provider plugin.

Problem: A new major provider release shipped with breaking changes to a resource's required arguments, causing every pipeline run to fail overnight with no code changes on the team's side.

Solution: Added a required_providers block pinning the provider to the last known-good version, then planned and tested the major upgrade separately before adopting it.

Outcome: CI pipeline runs became stable again immediately, and the provider upgrade was rolled out deliberately once compatibility was verified.

Security Team — Blocking a Vulnerable Provider Release

Scenario: A provider plugin used across the organization's Terraform configurations had a version with a disclosed security issue.

Problem: Some configurations had no version constraint and risked picking up the vulnerable release on the next terraform init.

Solution: Updated required_providers across affected configurations with a != constraint excluding the vulnerable version, combined with a ~> constraint to allow only safe patch releases.

Outcome: New initializations could no longer resolve to the vulnerable version, closing the exposure window without waiting for every team to manually intervene.

Startup — Reproducible Builds Across Developer Laptops

Scenario: A small engineering team noticed terraform plan produced different results depending on which developer's laptop ran it.

Problem: Provider versions weren't pinned, so different developers had initialized with different provider releases installed at different times.

Solution: Added explicit version constraints to required_providers and committed the .terraform.lock.hcl file to the repository.

Outcome: Every developer and CI run resolved to the same provider version, eliminating the inconsistent plan output.

Quick Recap

  • By default, terraform init installs the latest available provider version for each required provider.
  • The required_providers block inside terraform { } lets you pin or constrain provider versions per provider, using source and version.
  • Constraint operators: = (exact), != (exclude), < / <= / > / >= (bounds), and ~> (pessimistic — allows the given version or later within the same segment).
  • Constraints can be combined with commas to define a precise acceptable range.
  • ~> 1.2 allows any 1.x version >= 1.2; ~> 1.2.0 only allows 1.2.x patch releases.

Interview / Revision Notes

  • Q: What happens if no version constraint is specified for a provider? terraform init downloads the latest available version of that provider.
  • Q: Where do you configure provider version constraints? Inside a required_providers block, nested within a top-level terraform { } block.
  • Q: What does the != operator do in a version constraint? Excludes a specific version from being installed.
  • Q: What's the difference between ~> 1.2 and ~> 1.2.0? ~> 1.2 allows any 1.x version >= 1.2 (up to but excluding 2.0); ~> 1.2.0 only allows patch-level releases within 1.2.x.
  • Q: How would you require a version strictly between two values while excluding one specific version? Combine constraints with commas, e.g. version = "> 1.2.0, < 2.0.0, != 1.4.0".
  • Q: Why pin provider versions in production rather than always using the latest? Provider behavior can change between versions, and an unplanned upgrade could break a working configuration without warning.