terraform init -upgrade moves installed modules to the latest version your constraints allow. Plain terraform init keeps what is already installed unless you changed a module source or version. So a range constraint only picks up new releases when someone runs the upgrade, which makes that command a review point.
What the documentation states
HashiCorp's module configuration page says that changing a registry version or a source requires rerunning terraform init, and that terraform init -upgrade moves installed modules to the latest version allowed by the constraint. For Git sources without a ref, Terraform clones and uses the default branch referenced by HEAD, and a ref pins to a branch, SHA-1 hash or tag.
Three situations
| What you do | Result |
|---|---|
Run terraform init with nothing changed | Installed module versions stay as they are |
Edit version or source, run terraform init | New source or version is installed |
Run terraform init -upgrade with a range constraint | Newest version within the range is installed |
The upgrade flag is where silent drift enters a configuration that uses ranges. The range was agreed when the pin was written. The new version was not reviewed by anyone.
Why shared repositories care
In a shared repository with a range such as ~> 1.4 on an internal module, the next person to run upgrade pulls whatever the module team released last. That is acceptable if the module team follows semantic versioning consistently. HashiCorp says to use version ranges for modules your organization maintains if it does. If it does not, use exact versions.
For third party modules, HashiCorp advises pinning specific versions so updates happen when convenient. See pinning Terraform modules.
How to make upgrades reviewable
- Run upgrade on a branch, never directly on the default branch.
- Commit the resulting changes and open a pull request.
- In the pull request, read the release notes of each module that moved.
- Run a plan and read it. Any replaced object needs a human decision.
- Check that provider selections did not change unexpectedly. The lock file records those. See what the lock file protects.
- Merge only after required reviewers approve.
A scheduled bot can do steps 1 and 2 for you. See Dependabot for Terraform.
CI considerations
In CI, avoid running upgrade inside the same job that applies. The apply should use what was reviewed. See lock file read only in CI. A pipeline that upgrades and applies in one step makes the unreviewed version the production version.
What it does not tell you
The command does not say whether a new module version is safe or whether its release notes list every change. It shows only the selection. Judging the change is still work for reviewers, and a clean semver history does not replace reading the diff.
Where Safeguard fits
Safeguard's policies and gates can require reviewers and checks on pull requests that change module versions, and its evidence automation can record approvals.
Quick check
Does plain init upgrade modules?
No. Per HashiCorp, you need to rerun init when a source or version changes, and use the upgrade flag to move to the latest allowed version.
Is a git module without a ref pinned?
No. Terraform uses the default branch referenced by HEAD, so it can change.
Sources
Last reviewed: 19 October 2026.