Skip to content
Infrastructure as Code iac providers 5 min read

required_providers & Versions

Terraform configurations depend on providers — plugins that translate HCL into API calls against AWS, Azure, GCP, Kubernetes, and hundreds of other platforms. Without explicit pinning, Terraform downloads the newest provider that satisfies your constraints, which means two engineers (or two CI runs) can quietly end up on different versions. The terraform { required_providers } block makes those dependencies explicit and reproducible, and the .terraform.lock.hcl file freezes the exact versions and checksums. Getting this right is the difference between “works on my machine” and a build that behaves identically everywhere.

The required_providers block

Provider requirements live inside the top-level terraform block. Each entry maps a local name (how you refer to the provider in this configuration) to a source address and a version constraint. Modern Terraform (0.13+) requires the source attribute; relying on implicit sourcing is deprecated and unsupported in 1.x.

terraform {
  required_version = ">= 1.5.0"

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

provider "aws" {
  region = "us-east-1"
}

This same block works unchanged with OpenTofu, which resolves the hashicorp/* namespace from its own registry mirror.

Source addresses

A source address has three parts: [<HOSTNAME>/]<NAMESPACE>/<TYPE>. The hostname defaults to registry.terraform.io when omitted, the namespace is the organization that publishes the provider, and the type is the provider name.

Source addressHostnameNamespaceType
hashicorp/awsregistry.terraform.iohashicorpaws
hashicorp/googleregistry.terraform.iohashicorpgoogle
integrations/githubregistry.terraform.iointegrationsgithub
app.terraform.io/acme/internalapp.terraform.ioacmeinternal

The local name (the key in required_providers) is independent of the source type. They usually match, but you can rename a provider locally — for example to disambiguate two providers that both publish a type named dns. The local name is what you reference in provider blocks and resource provider arguments.

Version constraints

Version constraints are evaluated against published semantic versions. Terraform selects the newest version that satisfies every constraint across your configuration and modules. You can combine multiple comma-separated constraints.

OperatorMeaningExampleMatches
= (or bare)Exact version= 5.40.0only 5.40.0
!=Excludes a version!= 5.41.0anything except 5.41.0
>=, <=, >, <Comparison>= 5.0, < 6.0the entire 5.x line
~>Pessimistic / “allow rightmost increment”~> 5.40>= 5.40, < 6.0
~> (patch-only)Pin minor, allow patches~> 5.40.0>= 5.40.0, < 5.41.0

The ~> operator is the most common choice: ~> 5.40 lets you pick up new features and bug fixes within the 5.x major line while preventing a surprise jump to 6.0, where breaking changes are expected by semver convention.

required_providers {
  aws = {
    source  = "hashicorp/aws"
    version = ">= 5.0, < 6.0, != 5.41.0" # avoid a known-broken patch
  }
}

required_version for Terraform itself

required_version constrains the Terraform CLI version, not a provider. Use it to stop teammates running an incompatible CLI against a configuration that uses newer language features. It accepts the same constraint syntax.

terraform {
  required_version = ">= 1.5.0, < 2.0.0"
}

If the running CLI falls outside the constraint, Terraform refuses to proceed:

Output:

│ Error: Unsupported Terraform Core version

│ This configuration does not support Terraform version 1.4.6. To proceed,
│ either choose another supported Terraform version or update this version
│ constraint. Version constraints are normally set for good reason, so
│ updating the constraint may lead to other errors or unexpected behavior.

The .terraform.lock.hcl lock file

Version constraints define a range; the dependency lock file records the exact version selected plus cryptographic checksums for every platform. Terraform creates and updates .terraform.lock.hcl during terraform init. You should commit it to version control so every run — local, CI, or production — installs byte-identical providers.

# .terraform.lock.hcl — managed by Terraform, do not edit manually
provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.40.0"
  constraints = "~> 5.40"
  hashes = [
    "h1:K8gQH93nWnQ+v0eHvA7jX1m1zL0pq6kxN3yQ7sQ6m0c=",
    "zh:0a7e5b3...",
  ]
}

Running init resolves and locks the providers:

terraform init

Output:

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.40"...
- Installing hashicorp/aws v5.40.0...
- Installed hashicorp/aws v5.40.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Upgrading locked providers

The lock file is sticky: once a version is recorded, init keeps using it even if a newer one satisfies the constraint. To deliberately move forward, pass -upgrade:

terraform init -upgrade

To pre-populate hashes for every platform your team and CI use — avoiding “checksum not in lock file” failures across Linux, macOS, and Windows — record them all at once:

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

Never hand-edit .terraform.lock.hcl. If a teammate’s init fails with a checksum mismatch, regenerate the entry with terraform providers lock rather than deleting hashes — deleting them weakens the supply-chain protection the file provides.

Best Practices

  • Always set an explicit source and version for every provider — never rely on implicit resolution.
  • Prefer ~> to allow patch and minor updates while blocking major-version surprises.
  • Add a required_version constraint to keep the whole team on a compatible CLI.
  • Commit .terraform.lock.hcl to version control and review changes to it in pull requests.
  • Use terraform providers lock -platform=... to record hashes for every OS/arch in your team and CI matrix.
  • Upgrade providers intentionally with terraform init -upgrade, then test the resulting plan before merging.
  • Keep provider configuration (region, credentials) in provider blocks separate from the version-pinning required_providers block.
Last updated June 14, 2026
Was this helpful?