Fix Terraform Provider Version Conflicts

Provider version problems are some of the most confusing errors Terraform throws, mostly because the message rarely points at the file you actually need to change. The plan fails, CI turns red, and the constraint it complains about lives three modules deep. Once you understand how Terraform picks a provider version and what the lock file is doing, these stop being mysteries.

How Terraform selects provider versions #

Every provider your configuration touches is declared in a required_providers block, usually inside a terraform block:

hcl.hcl

terraform {
required_version = ">= 1.5.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.40"
}
random = {
source = "hashicorp/random"
version = ">= 3.5.0"
}
}
}

When you run terraform init, Terraform collects the constraints for each provider from the root module and every child module, intersects them, and picks the newest published version that satisfies all of them. It then records the exact chosen version and its checksums in .terraform.lock.hcl, the dependency lock file. On subsequent runs Terraform reads that lock file first and reuses the pinned version instead of re-solving, which is what keeps two engineers on the same version.

The constraint operators matter here:

= 5.40.0 pins one exact version.

>= 5.40 allows that version or anything newer.

~> 5.40 is the pessimistic operator: it allows 5.40.x but not 5.41.0. Written ~> 5.0, it allows any 5.x.

No operator (5.40.0) is treated as =.

Rule of thumb: use ~> for a sensible upgrade window in reusable modules, and let the lock file pin the exact build for reproducibility.

"No available releases match the given constraints" #

This is the conflict error. It almost always means two places in your dependency tree disagree. A common case: your root pins aws = "~> 5.40" while a shared module you pulled in still requires aws = "~> 4.0". There is no single AWS provider version that is both 5.x and 4.x, so the solver gives up.

To find the culprit, list where the constraints come from:

bash.bash

terraform providers

That prints a tree showing each module and the provider versions it asks for. Look for the module whose range does not overlap with the rest. The fix is to widen or align the constraints so an intersection exists, usually by upgrading the older module or loosening an overly strict pin. If the conflicting module is a third-party one you can't edit, you're stuck on a version that satisfies its range until it's updated, so that's often the signal to fork it or replace it.

"Provider was previously installed but now missing from the lock file" / checksum mismatch #

This one shows up after someone bumps a version or hand-edits the lock file. Terraform sees a provider in your config, finds an entry (or a partial entry) in .terraform.lock.hcl that no longer matches what it wants to install, and refuses to proceed rather than silently swapping binaries.

If the change is intentional, let Terraform move within your constraints and rewrite the lock file:

bash.bash

terraform init -upgrade

-upgrade tells Terraform to ignore the currently locked versions and re-select the newest allowed by your constraints, then update the checksums. Commit the resulting .terraform.lock.hcl. If it's not intentional (someone committed a bad merge of the lock file), reset the file from version control and run a plain terraform init.

Platform (H1:) checksum errors in CI #

Here's the one that catches teams out. You develop on a Mac, generate the lock file locally, commit it, and CI on Linux fails with something like "the local package does not match any of the checksums" or "Failed to install provider." The lock file only recorded the checksums for darwin_arm64, so when the Linux runner tries to install the linux_amd64 build, its hash isn't in the file.

The lock file can hold checksums for many platforms at once. Generate them for every OS/arch your team and pipelines use:

bash.bash

terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=darwin_amd64 \
-platform=windows_amd64

This fetches each provider for each listed platform and writes all the H1: checksums into .terraform.lock.hcl. Commit the result and the CI checksum error goes away. Add this to your onboarding docs and re-run it whenever you bump a provider, so a laptop-only checksum never sneaks into the repo again.

Should you commit .terraform.lock.hcl? Yes #

Commit it. The lock file is the mechanism that guarantees every developer and every pipeline run installs the identical provider builds. Leaving it out of version control reintroduces the version drift you were trying to avoid, where one engineer plans against AWS provider 5.40.1 and another against 5.44.0, producing plan diffs that have nothing to do with the actual change.

The .terraform/ directory (the downloaded provider binaries and cached modules) is different. That is a local cache and belongs in .gitignore, never in the repo.

When things get wedged #

Sometimes init keeps failing even after you've fixed the constraints and the lock file, usually because the local cache is in a half-installed state. Clear it and start clean:

bash.bash

rm -rf .terraform/
terraform init

Deleting .terraform/ is safe: it only removes downloaded binaries and cached modules, not your state or your lock file. Terraform re-downloads everything on the next init. If you still hit a checksum wall after that, re-run the multi-platform terraform providers lock above so the fresh install matches what's recorded.

For the wider set of init, plan, and state failures beyond provider versions, see the Terraform error troubleshooting guide. If you're weighing the ecosystem more broadly, our Terraform vs OpenTofu comparison is a useful companion.

The call we'd make #

Pin providers with ~> in your required_providers blocks, commit .terraform.lock.hcl, and gitignore .terraform/. Standardize on running terraform providers lock with every platform your team and CI use, so the lock file is complete the day it's committed rather than the day CI first breaks. When a conflict appears, run terraform providers to find the disagreeing module before touching anything, and reach for terraform init -upgrade only when you actually intend to move versions. Treat the lock file as a first-class artifact, review changes to it in pull requests, and most of these errors simply stop happening.