Someone in procurement forwards you a renewal quote. Someone in legal asks whether your CI runners "redistribute" a BUSL-licensed binary. Meanwhile your platform team is still pinned to Terraform 1.5.7 because nobody wanted to make the call. That is the real shape of the OpenTofu vs Terraform question in most organisations: not an ideology debate, but a stalled decision with a cost attached to every month it stays stalled.
This comparison is written for the person who has to make that call and defend it. We will set the decision criteria first, then go head-to-head on licensing, feature divergence, registry supply chain, and managed platform capability — and finish with a recommendation matrix plus the edge cases that break it.
OpenTofu is a community-governed, MPL 2.0 fork of Terraform, hosted by the Linux Foundation and forked from the last Mozilla-licensed Terraform code in 2023. It uses the same HCL language, the same state format, and the same provider protocol, under independent maintainers rather than a single vendor.
What changed between 2023 and the IBM acquisition
Three events, in order, created today's situation.
In August 2023, HashiCorp relicensed Terraform from MPL 2.0 to the Business Source License 1.1. Each BUSL release carries an additional-use grant and converts to MPL 2.0 four years after publication, as documented in the HashiCorp license FAQ. The practical effect: production use is fine, but building something competitive with HashiCorp's products on top of the code is not.
In September 2023, the fork formalised. The Linux Foundation announced OpenTofu as a neutrally governed project, and OpenTofu 1.6.0 shipped as generally available in January 2024.
In February 2025, IBM completed its acquisition of HashiCorp. That did not revert the licence. It did change the strategic picture: Terraform is now a component of a hybrid-cloud automation portfolio with enterprise sales motion, Red Hat adjacency, and OpenShift deployment targets for Terraform Enterprise. If you were waiting for the acquisition to resolve the licence question, it has resolved it — by confirming BUSL is permanent for current releases.
What did not change
The HCL language. The provider plugin protocol. State format version 4. The .terraform.lock.hcl file name. Your modules still work on both tools. That shared baseline is why this is a genuine choice and not a rewrite.
OpenTofu vs Terraform: which five criteria actually decide it?
Most teams over-weight the licence and under-weight switching cost. Score your situation against these five instead, in this order.
| Criterion | Ask yourself | Points to |
|---|---|---|
| Licence exposure | Do we ship, embed, or resell IaC tooling? | OpenTofu if yes |
| Platform dependency | Do we rely on Sentinel, Stacks, no-code modules, or TFE? | Terraform if yes |
| Feature direction | Do we need state encryption or provider for_each? |
OpenTofu if yes |
| Ecosystem fit | Does our CI/CD or orchestration layer support both? | Usually neutral |
| Switching cost | How many workspaces, modules, and pipelines change? | Terraform if very high |
The ordering matters. Licence exposure and platform dependency are usually binary and non-negotiable; the other three are gradients you can engineer around.
Here is that logic as a flow. Read it as an ordered sequence:
- Start from your existing IaC estate.
- If you depend on HCP Terraform or Terraform Enterprise features, stay on Terraform.
- Otherwise, check whether your use case is clearly inside the BUSL additional-use grant.
- If it is not, move to OpenTofu.
- If it is, check whether you need an OpenTofu-only feature.
- If neither pushes you, run a dual-binary pilot and decide on evidence.
How do the licences differ in practice?
For a bank running Terraform to provision its own Azure landing zones, BUSL changes nothing. The additional-use grant permits production use. Stop worrying.
The exposure is narrower than the internet suggests, but it is real in four shapes:
- You are a managed service provider running Terraform plans on behalf of customers as a billable product.
- You embed the binary in a commercial platform, appliance, or SaaS control plane.
- You build an IaC orchestration product — exactly the "competitive" case the licence names.
- You are a vendor with procurement rules that reject non-OSI licences outright, regardless of grant.
OpenTofu sidesteps all four. It remains under MPL 2.0, a recognised OSI licence, with trademark and governance held by the Linux Foundation rather than a commercial entity. For regulated buyers, the governance story is often worth more than the licence text: no single vendor can relicense it again.
One nuance worth putting in your decision memo. BUSL is time-limited, not permanent. The 2023 releases convert to MPL 2.0 in 2027, and each later release follows on its own four-year clock. If your only concern is the licence of an old pinned version, that concern expires. If your concern is the licence of next year's features, it does not.
Where have the two CLIs diverged?
Two years post-fork, the codebases still share most of their surface area — but each has shipped features the other lacks. This is now the strongest technical input to the decision.
OpenTofu-only capabilities
Client-side state and plan encryption landed in OpenTofu 1.7 and has no Terraform CLI equivalent. OpenTofu's state encryption docs cover key providers for PBKDF2 passphrases, AWS KMS, GCP KMS, and OpenBao. If your threat model includes "someone reads the state blob and harvests credentials", this single feature can end the debate:
terraform {
encryption {
key_provider "aws_kms" "state_key" {
kms_key_id = "alias/tofu-state"
region = "westeurope"
key_spec = "AES_256"
}
method "aes_gcm" "primary" {
keys = key_provider.aws_kms.state_key
}
state {
method = method.aes_gcm.primary
enforced = true
}
plan {
method = method.aes_gcm.primary
enforced = true
}
}
}
Provider iteration followed in OpenTofu 1.9, which allows for_each on a provider block — the long-requested answer to multi-region and multi-subscription fan-out that Terraform still solves with hand-written aliases. The OpenTofu 1.9 release notes also introduced -exclude, the inverse of -target:
variable "regions" {
type = set(string)
default = ["westeurope", "northeurope", "swedencentral"]
}
provider "azurerm" {
for_each = var.regions
alias = "regional"
subscription_id = var.subscription_id
features {}
}
module "hub" {
for_each = var.regions
source = "./modules/hub"
location = each.key
providers = { azurerm = provider.azurerm.regional[each.key] }
}
OpenTofu also shipped early variable evaluation for backend and module-source configuration in 1.8, .tofu file overrides, and OCI-registry distribution for providers and modules in 1.10.
Terraform-only capabilities
Terraform's investment has gone into secret hygiene and the managed platform. Ephemeral resources arrived in 1.10 and write-only arguments in 1.11, both designed so that sensitive values never reach state at all. The Terraform ephemeral resources documentation describes the lifecycle:
ephemeral "random_password" "db" {
length = 32
}
resource "azurerm_mysql_flexible_server" "main" {
name = "itpro-mysql"
resource_group_name = azurerm_resource_group.main.name
location = "westeurope"
administrator_login = "msradmin"
administrator_password_wo = ephemeral.random_password.db.result
administrator_password_wo_version = 1
}
Notice the philosophical split. Terraform keeps secrets out of state; OpenTofu encrypts the state that holds them. Both are defensible. If you already run a secret manager and want nothing persisted, Terraform's model is cleaner. If you have years of existing state containing sensitive attributes you cannot refactor away, encryption is the pragmatic win.
Where they converged anyway
Both tools adopted provider-defined functions, both support native S3 state locking via use_lockfile (retiring the DynamoDB table pattern), and both track the same provider protocol version. Divergence is real but slow, and it is concentrated at the edges rather than the core.
Which registry are you actually trusting?
This is the criterion teams skip, and the one that bites during audits.
Terraform resolves providers through registry.terraform.io, whose terms of service restrict use to Terraform itself — which is precisely why OpenTofu had to build its own. OpenTofu resolves through registry.opentofu.org, a brokered registry that indexes provider and module releases from their upstream source repositories; the implementation is public in the opentofu/registry repo.
The implications are concrete:
- Provider availability is near-identical for mainstream providers, because both registries ultimately serve the same upstream release artifacts.
- Niche or newly published providers can lag on one side. Verify your long tail before committing, not after.
- Your lock file records the registry host, so switching binaries rewrites every provider source address in
.terraform.lock.hcl. - Air-gapped environments need a filesystem mirror or network mirror either way; OpenTofu's OCI support gives you a third option if you already run Harbor or ACR.
Run this before you decide anything, on your largest workspace:
# Inventory every provider and version your estate actually depends on
grep -rhoP 'source\s*=\s*"\K[^"]+' --include='*.tf' . | sort -u > providers.txt
# Confirm each one resolves on the OpenTofu registry
while read -r p; do
ns="${p%/*}"; name="${p##*/}"
code=$(curl -s -o /dev/null -w '%{http_code}' \
"https://registry.opentofu.org/v1/providers/${ns}/${name}/versions")
printf '%-40s %s\n' "$p" "$code"
done < providers.txt
Anything returning a non-200 is a migration blocker. Resolve it with a mirror, a vendored provider, or a decision to stay.
HCP Terraform vs the OpenTofu platform ecosystem
Here the comparison stops being symmetrical. OpenTofu is a CLI and nothing else — by design. Terraform ships with a commercial platform behind it, and that platform is where IBM's investment is visible.
| Capability | HCP Terraform / Enterprise | OpenTofu path |
|---|---|---|
| Remote state + runs | Native, with free tier to 500 resources | Backend of choice; runner of choice |
| Policy as code | Sentinel and OPA policy sets | OPA/Conftest in CI, or a third-party platform |
| Private registry | Native | Git sources, OCI registry, or third-party |
| RBAC and audit | Native, SSO-integrated | Supplied by your platform layer |
| Drift detection | Native health assessments | Scheduled plan jobs you own |
| Stacks | HCP Terraform capability | No equivalent |
| Self-hosted | Terraform Enterprise, including OpenShift | Any CI runner you already have |
HashiCorp documents the resource-under-management pricing model in its HCP Terraform cost estimation guide, and that model is the hinge for most finance conversations: cost scales with managed resource count, not seats.
The OpenTofu answer is to assemble the equivalent. Spacelift, env0, Scalr, Digger, and self-hosted Atlantis all execute OpenTofu, and Terragrunt supports pointing at either binary. That is a genuine option, not a downgrade — but be honest in your business case. You are trading a licence line item for an integration project plus ongoing ownership of policy, RBAC, and audit plumbing.
One hard constraint: HCP Terraform does not support OpenTofu as a client. OpenTofu retained a cloud block that speaks the TFE API, which third-party backends implement, but you cannot keep HCP Terraform workspaces and swap the binary underneath. Platform dependency really is binary.
How to migrate a live workspace to OpenTofu
If the criteria point to OpenTofu, migrate one workspace properly before touching the estate. The sequence, as an ordered list:
- Capture a baseline plan with your current Terraform version.
- Back up remote state out-of-band and record the serial number.
- Run
tofu initagainst the existing working directory. - Regenerate the lock file for all target platforms.
- Plan with OpenTofu and require an empty diff.
- Cut CI over to the
tofubinary. - Keep a tested rollback to Terraform until two clean apply cycles pass.
The commands, verbatim:
# 1. Baseline and state backup — do not skip this
terraform init -input=false
terraform plan -out=baseline.tfplan
terraform show -json baseline.tfplan > baseline.json
terraform state pull > state-backup-$(date -u +%Y%m%dT%H%M%SZ).json
# 2. Initialise with OpenTofu against the same backend
tofu init -input=false -upgrade
# 3. Rebuild provider hashes for every platform your CI uses
tofu providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=windows_amd64
# 4. Prove equivalence: this must report no changes
tofu plan -detailed-exitcode
echo "exit=$?" # 0 = no changes, 2 = drift, 1 = error
An exit code of 0 on step 4 is your go/no-go signal. An exit code of 2 means something re-read differently — usually a provider version bump pulled in by -upgrade, so pin versions tightly and retry before you investigate anything deeper.
For Windows-first shops, the same gate in PowerShell:
tofu plan -detailed-exitcode
switch ($LASTEXITCODE) {
0 { Write-Host 'Equivalent — safe to cut over' -ForegroundColor Green }
2 { Write-Error 'Plan drift detected — investigate before apply'; exit 1 }
default { Write-Error 'Plan failed'; exit 1 }
}
Keep both binaries honest in CI
While you run dual-binary, let the pipeline tell you when the tools disagree instead of discovering it during a release:
name: iac-equivalence
on: [pull_request]
jobs:
plan:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
tool: [terraform, opentofu]
steps:
- uses: actions/checkout@v4
- if: matrix.tool == 'terraform'
uses: hashicorp/setup-terraform@v3
with: { terraform_version: 1.11.4, terraform_wrapper: false }
- if: matrix.tool == 'opentofu'
uses: opentofu/setup-opentofu@v1
with: { tofu_version: 1.10.0, tofu_wrapper: false }
- name: Plan
run: |
BIN=terraform; [ "${{ matrix.tool }}" = "opentofu" ] && BIN=tofu
$BIN init -input=false -backend=false
$BIN validate
$BIN plan -input=false -lock=false -detailed-exitcode
Note -backend=false and -lock=false: both tools share one state file during the overlap, so only one of them may hold the lock or write state. Run the second as a validation-only pass.
Pitfalls to avoid when you switch binaries
These are the failures that actually show up in post-mortems.
Hand-editing the lock file. Provider source addresses change registry host, so a sed-and-pray edit produces hash mismatches that fail in CI but pass on the laptop that generated them. Always regenerate with tofu providers lock and commit the result.
Forgetting non-default platforms. If your developers run macOS ARM and CI runs Linux AMD64, a lock file generated on one will block the other. Enumerate every platform explicitly.
Reading required_version as a feature gate. Version numbers no longer mean the same thing across tools. A module declaring required_version = ">= 1.10.0" is satisfied by both Terraform 1.10 and OpenTofu 1.10, but only one of them has ephemeral resources. Gate on the feature, and keep shared modules to the common language subset.
Treating state encryption as reversible. Once you enable enforced = true, Terraform can no longer read that state. That is a one-way door — enable it after the migration is accepted, never during.
Mixed binaries on one state file. Two tools applying against the same remote state is the fastest route to a corrupted serial. Enforce one writer per workspace with pipeline gates, not with a Slack message.
Leaving .tofu overrides in shared modules. The OpenTofu-only file extension is excellent for progressive migration and poison for any module a Terraform consumer might import.
Assuming the wrappers follow. Terragrunt, Atlantis, pre-commit hooks, tflint configs, policy bundles, and your own helper scripts all hard-code a binary name somewhere. Grep for terraform as a command invocation across your tooling repos before you declare the migration complete.
Forgetting the licence audit trail. If the licence drove the decision, record it. Capture the version you left, the date, and the reasoning — because the first person to ask will be an auditor, not an engineer.
When to choose Terraform, and when to choose OpenTofu
| Your situation | Choose | Why |
|---|---|---|
| Heavy HCP Terraform or TFE user | Terraform | Platform features have no OpenTofu equivalent |
| Sentinel policies already in production | Terraform | Rewriting to OPA is the real migration cost |
| Secret hygiene via ephemeral/write-only | Terraform | Keeps sensitive values out of state entirely |
| IBM-aligned estate, enterprise support needed | Terraform | Single vendor, single support path |
| MSP or product embedding IaC | OpenTofu | BUSL "competitive use" is a genuine blocker |
| Procurement requires an OSI licence | OpenTofu | MPL 2.0, Linux Foundation governance |
| State encryption is a compliance requirement | OpenTofu | Native, enforced, key-provider backed |
| Heavy multi-region or multi-subscription fan-out | OpenTofu | Provider for_each removes alias sprawl |
| Plain CLI plus your own CI, no platform spend | OpenTofu | Lower lock-in, equivalent capability |
| Mature estate, no licence exposure, no blockers | Either — stay put | Switching cost without a payoff is waste |
Edge cases that invert the matrix
You are a very small team with no platform engineer. HCP Terraform's free tier up to 500 resources beats assembling remote state, locking, RBAC, and drift detection yourself. Licence purity is a luxury when nobody owns the plumbing.
You are an ISV selling to the public sector. The OSI-licence requirement in tender documents can override every technical argument on the Terraform side. Check the tender text before the feature matrix.
You need both features. Some teams genuinely want write-only arguments and provider iteration. Split by workload rather than compromising: keep secret-heavy workspaces on Terraform, move fan-out-heavy landing zones to OpenTofu, and accept two binaries as a long-term posture. It is more operational overhead than one tool, and far less than the wrong tool.
Your estate is already Terragrunt-managed. Your switching cost is unusually low, because the binary is already an abstraction. Pilot sooner than you think you need to.
What to do before your next planning cycle
Three concrete actions, each under a day of work:
- Classify your licence exposure in writing. One page: do we embed, resell, or compete? Most teams discover the answer is no, and that reframes the whole discussion from legal to technical.
- Run the registry availability check and a single-workspace
tofu planon your least critical environment. An empty diff is the cheapest evidence you will ever buy, and a non-empty one tells you exactly which provider is the blocker. - Price the platform layer honestly. Compare your HCP Terraform resource-under-management cost against the engineering time to own policy, RBAC, and drift detection yourself. If the numbers are close, stay where you are — the right answer to OpenTofu vs Terraform is frequently "neither switch is worth the quarter it would consume."
The fork is stable, both tools are healthy, and the migration path in both directions still works. That means this is no longer an urgent decision. It is just an unmade one — and a one-page memo plus one test workspace is enough to close it.
Sources
- HashiCorp licence FAQ — BUSL 1.1 terms and additional-use grant
- Linux Foundation — announcing OpenTofu
- IBM newsroom — HashiCorp acquisition close, February 2025
- OpenTofu documentation — state and plan encryption
- OpenTofu 1.9 release notes — provider
for_eachand-exclude - Terraform documentation — ephemeral resources
- HCP Terraform — resources-under-management cost model
- Terraform Registry terms of service
- opentofu/registry — brokered provider and module registry
Leave a Reply