| Tested on | Ubuntu 26.04 LTS (Resolute Raccoon) |
|---|---|
| Package | terraform 1.15.8-1hashicorp/local provider 2.5.0 and 2.9.0 |
| Applies to | Any host with Terraform installed |
| Lab environment | Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu |
| Privilege | Normal user |
| Scope | Provider version constraints in required_providers, .terraform.lock.hcl, terraform init vs init -upgrade, terraform providers lock, checksums, Git workflow, and common lock errors. Does not cover provider aliases, authentication, full terraform init reference, module version pinning, or remote backends. |
| Related guides | Terraform lab environment on Ubuntu Install Terraform on Ubuntu Terraform HCL syntax Terraform Associate certification course |
A single terraform block often lists both required_version and required_providers with a version argument:
terraform {
required_version = ">= 1.12.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}Which line controls the Terraform CLI, and which line controls the provider plugin? Teams confuse these three layers constantly. This guide separates Terraform CLI version, provider version constraints, and .terraform.lock.hcl, then walks through init, upgrade, multi-platform locking, and recovery on Ubuntu.
terraform version is not available yet. Examples use the hashicorp/local provider so you do not need cloud credentials.
Terraform CLI version vs provider version
Three different settings answer three different questions:
| Setting or file | Controls |
|---|---|
required_version |
Terraform CLI binary compatibility |
required_providers version |
Allowed provider versions (constraint range) |
.terraform.lock.hcl |
Selected provider version and package checksums |
required_version never pins a provider. required_providers.version never pins the Terraform binary. The lock file never replaces either constraint — it records what terraform init already chose within those rules.
Keep that table in mind when you read init output, review pull requests that touch .terraform.lock.hcl, or debug version errors.
Lab directory for this guide
Create a dedicated working directory under your lab tree:
mkdir -p ~/terraform-labs/terraform-provider-version-lock-fileMove into it — the commands below assume this path:
cd ~/terraform-labs/terraform-provider-version-lock-fileAdd a minimal resource so terraform plan has something to evaluate later:
resource "local_file" "demo" {
content = "lock lab"
filename = "${path.module}/demo.txt"
}Save that block in main.tf. Put version metadata in versions.tf:
terraform {
required_version = ">= 1.12.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}The source address tells Terraform which registry namespace owns the plugin. The version string is a constraint, not an exact pin unless you use =.
Configure a provider version constraint
The version argument inside required_providers accepts one or more constraint strings. Terraform evaluates them when it resolves provider packages during terraform init.
Run init to download the provider and create the lock file:
terraform initSample output:
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.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.
Terraform has been successfully initialized!Terraform selected hashicorp/local v2.9.0 — the newest release that satisfies ~> 2.5 at the time of this lab run. Your registry may offer a different latest 2.x version later; the constraint syntax stays the same.
Terraform version constraint operators
Provider constraints use the same operator vocabulary Terraform documents for module versions. Each operator defines which releases are acceptable:
| Operator | Example | Acceptable versions (illustration) |
|---|---|---|
= |
= 2.5.0 |
Exactly 2.5.0 |
!= |
!= 2.4.0 |
Any release except 2.4.0 |
> |
> 2.4.0 |
2.4.1, 2.5.0, 3.0.0, … |
>= |
>= 2.4.0 |
2.4.0, 2.9.0, 3.0.0, … |
< |
< 2.6.0 |
2.5.9, 2.0.0, … (not 2.6.0) |
<= |
<= 2.5.0 |
2.5.0 and older |
~> |
~> 2.5 |
2.5.0 through 2.x (not 3.0.0) |
The pessimistic constraint operator ~> is the one teams reach for most often, and the one that causes the most mistakes if you guess the range.
With ~>, Terraform allows the right-most specified component to increase while keeping the preceding components within the same compatibility range:
~> 2.5means>= 2.5.0and< 3.0.0— any 2.x at or above 2.5.0.~> 2.5.1means>= 2.5.1and< 2.6.0— only 2.5.x from 2.5.1 upward.~> 1.2.3means>= 1.2.3and< 1.3.0.
Combine constraints with commas when you need a narrower band, for example >= 2.4.0, < 2.6.0.
What is .terraform.lock.hcl?
After the first successful init, Terraform writes .terraform.lock.hcl in the module root. List the directory to confirm it appeared:
ls -la .terraform.lock.hclSample output:
-rw-r--r-- 1 user user 1234 Aug 11 18:45 .terraform.lock.hclInspect the provider stanza:
sed -n '1,20p' .terraform.lock.hclSample output:
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/local" {
version = "2.9.0"
constraints = "~> 2.5"
hashes = [
"h1:9rBZCMNpxKwMlRbWH2QpwD3kqUCAejdOZQ/aiiDObXQ=",
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
...
]
}The lock file records:
- Selected version —
version = "2.9.0"is the build init installed. - Configured constraints —
constraints = "~> 2.5"mirrors yourrequired_providersblock at lock time. - Hashes — checksums for provider packages Terraform verified (see the checksum section below).
- Maintenance — Terraform updates this file during init and
terraform providers lock; treat manual hash edits as a last resort.
Version constraint vs locked version
A broad constraint such as version = ">= 2.0" allows many releases. The lock file still records one selected build — for example 2.9.0 — until you change constraints or run an upgrade init.
Run init again without flags to see how Terraform treats an existing lock:
terraform initSample output:
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.9.0
Terraform has been successfully initialized!Ordinary terraform init reuses the locked version even when the registry publishes a newer release that still satisfies ~> 2.5. That stability is intentional — teammates and CI should not silently pick up a new provider on every init.
To allow Terraform to reconsider versions inside the constraint, pass -upgrade:
terraform init -upgradeWhen the locked version is already the newest match, output stays on the same release:
- Finding hashicorp/local versions matching "~> 2.5"...
- Using previously-installed hashicorp/local v2.9.0The upgrade path matters most after you widen a constraint or when a newer provider was published since the lock was written. The end-to-end lab below shows a lock moving from 2.5.0 to 2.9.0.
Should .terraform.lock.hcl be committed to Git?
For shared root configurations, commit the lock file. It gives everyone the same provider selection and checksum set, makes dependency bumps reviewable in pull requests, and lets CI verify package integrity.
Check whether Git sees the new file:
git status --short .terraform.lock.hclSample output on a fresh repo:
?? .terraform.lock.hclStage it like any other tracked artifact:
git add .terraform.lock.hclDo not commit .terraform/ — only the lock file and your .tf sources belong in version control. The dependency lock file belongs to the overall configuration and is stored in the root module working directory. Reusable child modules therefore generally do not publish their own lock file.
What does the lock file actually lock?
.terraform.lock.hcl tracks provider plugins resolved from provider registries (or mirrors). It does not lock:
- Remote module version selections from
moduleblocks - Terraform CLI version (
required_versionis enforced separately at init) - Backend or workspace configuration
Module versions come from the version argument on module blocks and the module registry. Provider locking and module pinning are related ideas but different mechanisms — module version depth belongs in the modules lesson track.
Provider checksums in .terraform.lock.hcl
Each provider entry includes a hashes list with two prefix styles:
zh:— checksum based on the provider distribution ZIP archive.h1:— checksum based on the contents of the provider package.
Terraform compares downloaded packages against these values. If a mirror serves a tampered or wrong artifact, init fails checksum verification instead of running an unexpected binary.
Do not hand-edit hash lines to “fix” init errors. Regenerate checksums with terraform init, terraform init -upgrade, or terraform providers lock after you intentionally change provider versions or platforms.
Pre-populate checksums with terraform providers lock
Teams that develop on Linux laptops and deploy from CI on the same architecture still benefit from an explicit lock refresh when you add platforms. The terraform providers lock command fetches provider packages and writes checksums without running a full init workflow.
From your lab directory with an existing lock file, add checksums for two Linux platforms:
terraform providers lock -platform=linux_amd64 -platform=linux_arm64Sample output:
- Fetching hashicorp/local 2.9.0 for linux_amd64...
- Retrieved hashicorp/local 2.9.0 for linux_amd64 (signed by HashiCorp)
- Fetching hashicorp/local 2.9.0 for linux_arm64...
- Retrieved hashicorp/local 2.9.0 for linux_arm64 (signed by HashiCorp)
- Obtained hashicorp/local checksums for linux_amd64; All checksums for this platform were already tracked in the lock file
- Obtained hashicorp/local checksums for linux_arm64; Additional checksums for this platform are now tracked in the lock file
Success! Terraform has updated the lock file.
Review the changes in .terraform.lock.hcl and then commit to your
version control system to retain the new checksums.Commit the updated lock file so both platforms can verify downloaded provider packages against checksums already recorded in the lock file.
Upgrade a Terraform provider safely
Treat provider upgrades as a deliberate change, not a side effect of routine init.
1. Inspect constraints in versions.tf (or your required_providers file).
2. Inspect the lock file — note the current version = "..." line.
3. Widen or adjust the constraint if the target release is outside the current range.
4. Run upgrade init:
terraform init -upgrade5. Review the lock diff — confirm the version line and any new h1: entries match what you expected.
6. Validate and plan before apply:
terraform validateA successful validate ends with:
Success! The configuration is valid.Then preview infrastructure changes introduced by the new provider build:
terraform planProvider upgrades can change default behaviors or schemas even when your .tf files are unchanged. Read the provider changelog, review the plan output, and run apply only after the diff looks correct.
Downgrade or pin an exact provider version
Exact pins use = with a full semantic version:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "= 2.5.0"
}
}
}Run init to lock that build:
terraform initSample output:
- Installing hashicorp/local v2.5.0...
- Installed hashicorp/local v2.5.0 (signed by HashiCorp)The lock file now records version = "2.5.0".
Exact pins help when you are reproducing a production incident or holding a known-good build during a provider bug. They also reduce flexibility — every bump requires editing the constraint and running terraform init -upgrade.
If you widen the constraint later — for example from = 2.5.0 to ~> 2.5 — ordinary init keeps 2.5.0 until upgrade init runs:
terraform initSample output:
- Reusing previous version of hashicorp/local from the dependency lock filePull the newer acceptable release with:
terraform init -upgradeSample output:
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.The lock file version line moves from 2.5.0 to 2.9.0. That before/after pair is the core lesson: constraints define what is allowed; the lock file records what is selected.
For reusable modules, prefer a minimum-compatible provider constraint such as >= 2.4.0. Let the root module apply tighter ~> bounds and own the final provider selection.
required_version constrains the Terraform CLI
required_version belongs in the same terraform block but answers a different question — whether the installed Terraform binary is new enough (or within a supported band).
Example:
terraform {
required_version = ">= 1.12.0"
}If you set an impossible floor, init fails before provider resolution starts:
terraform {
required_version = ">= 99.0.0"
}Run init to see the core version guardrail:
terraform initSample output:
Error: Unsupported Terraform Core version
on versions.tf line 2, in terraform:
2: required_version = ">= 99.0.0"
This configuration does not support Terraform version 1.15.8. To proceed,
either choose another supported Terraform version or update this version
constraint.Restore a realistic constraint (>= 1.12.0 on this lab) before continuing. required_version is essential for team consistency, but it is not a provider version mechanism — keep it in the comparison table at the top of this guide, not mixed into provider upgrade runbooks.
End-to-end verification lab
This sequence ties constraint, lock, ordinary init, and upgrade init together. Use a fresh directory so the output is easy to follow:
mkdir -p ~/terraform-labs/terraform-provider-version-lock-file-upgradeSwitch into that directory for the rest of the lab:
cd ~/terraform-labs/terraform-provider-version-lock-file-upgradeAdd the same local_file resource in main.tf as earlier. Start with an exact pin in versions.tf:
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "= 2.5.0"
}
}
}Step 1 — first init selects and locks 2.5.0:
terraform initStep 2 — widen the constraint to ~> 2.5 but do not upgrade yet. Edit versions.tf, then run ordinary init:
terraform initTerraform reuses 2.5.0 from the lock file even though ~> 2.5 allows newer 2.x releases.
Step 3 — upgrade init picks the newest acceptable 2.x:
terraform init -upgradeThe lock file version changes to 2.9.0 (or whatever latest 2.x your registry offers). Commit that diff, then run terraform validate and terraform plan before any apply in a real environment.
broad constraint → terraform init → lock records selection
↓
change constraint (wider) → ordinary init keeps lock
↓
terraform init -upgrade → lock updates to newer acceptable releaseCommon version and lock file errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Locked provider does not match configured constraint | Lock file records 2.5.0 but constraint is now = 2.4.0 |
Align constraint with the locked version, or run terraform init -upgrade after fixing the constraint to an allowed range |
| Provider package checksum mismatch | Mirror or cache served a different artifact than the lock file expects | Restore a correct mirror, or run terraform providers lock / terraform init -upgrade to refresh hashes; do not edit hashes by hand |
| Unsupported Terraform Core version | required_version excludes your CLI |
Install a supported Terraform release or relax required_version |
| No available provider version satisfies constraints | Constraint such as = 99.0.0 has no registry release |
Pick a version that exists on the registry, or widen the constraint |
| Platform package unavailable | Provider does not publish a build for the target OS or architecture | Choose a supported provider version or platform |
| Checksum missing for another platform | Lock file does not yet contain sufficient hashes for your platform | Run terraform providers lock -platform=... on a networked machine, commit the lock, or fix mirror/offline paths |
Locked provider does not match configured constraint
Start from a lock that records 2.5.0, then tighten the constraint to = 2.4.0:
terraform initSample output:
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/local: locked provider registry.terraform.io/hashicorp/local
2.5.0 does not match configured version constraint 2.4.0; must use
terraform init -upgrade to allow selection of new versionsEither restore a compatible constraint or run terraform init -upgrade after setting a constraint that includes the version you want.
Provider package checksum mismatch
When a filesystem mirror serves the wrong file, Terraform refuses to install it. Init with a populated lock file and a bad mirror artifact produces:
terraform initSample output:
Error: Failed to install provider
Error while installing hashicorp/local v2.9.0: the local package for
registry.terraform.io/hashicorp/local 2.9.0 doesn't match any of the
checksums previously recorded in the dependency lock file (this might be
because the available checksums are for packages targeting different
platforms); for more information:
https://developer.hashicorp.com/terraform/language/files/dependency-lock#checksum-verificationFix the mirror artifact or regenerate locks from a trusted network path.
No available provider version satisfies constraints
Set version = "= 99.0.0" in required_providers, then init:
terraform initSample output:
Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/local: no available releases match the given constraints 99.0.0Choose a published version or widen the constraint.
Recover from an incorrect lock file
Do not delete .terraform.lock.hcl as a first troubleshooting step. Start with inspection:
- Read
required_providersconstraints in your.tffiles. - Read the
versionandconstraintslines in.terraform.lock.hcl. - Decide whether the constraint or the lock is wrong.
Then pick the smallest fix:
- Constraint typo — fix the version string, run
terraform init -upgradeif you need a new selection. - Stale lock after intentional bump — run
terraform init -upgrade, review the diff, commit. - Missing platform checksums — run
terraform providers lock -platform=...for each CI or laptop platform, commit. - Irreconcilably corrupt lock file — back up
.terraform.lock.hcl, remove the lock file, and runterraform initto create a new one. Review the resulting provider selections before committing it. Removing.terraform/alone does not regenerate the lock file; Terraform continues honoring an existing.terraform.lock.hcl.
Regenerating the entire lock file is appropriate when the file is clearly broken and you cannot reconcile constraints. Prefer targeted init -upgrade or providers lock when possible so unrelated provider entries stay stable.
References
- Dependency lock file (.terraform.lock.hcl)
- Provider requirements
- Version constraints
- terraform init command
- terraform providers lock command
Summary
You separated three layers that beginners often merge: required_version guards the Terraform CLI, required_providers.version defines acceptable provider releases, and .terraform.lock.hcl records the selected version and acceptable package checksums after init. Constraint operators — especially ~> — describe ranges; the lock file records one selected version until you change constraints or run terraform init -upgrade.
On Ubuntu you watched hashicorp/local move from a pessimistic ~> 2.5 constraint to a concrete 2.9.0 lock entry, saw ordinary init reuse that lock when the registry had newer compatible releases, and used upgrade init to move from an exact 2.5.0 pin to a newer 2.x build. You also pre-populated multi-platform checksums with terraform providers lock and staged the lock file for Git so teammates select the same provider version and verify packages against the same recorded hashes.
The mistake to avoid is treating required_version as a provider pin, or assuming init always downloads the newest provider. Commit .terraform.lock.hcl for shared roots, review lock diffs like application code, and run validate and plan after every provider upgrade before you apply in production. Next in this course track: Terraform state fundamentals and the core init/plan/apply workflow commands.

