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:

  1. Start from your existing IaC estate.
  2. If you depend on HCP Terraform or Terraform Enterprise features, stay on Terraform.
  3. Otherwise, check whether your use case is clearly inside the BUSL additional-use grant.
  4. If it is not, move to OpenTofu.
  5. If it is, check whether you need an OpenTofu-only feature.
  6. If neither pushes you, run a dual-binary pilot and decide on evidence.
decision estate IaC estate today platform Needs HCP/TFE features? estate->platform licence BUSL grant covers us? platform->licence no stay Stay on Terraform platform->stay yes feature Needs tofu-only feature? licence->feature yes move Move to OpenTofu licence->move no feature->move yes pilot Dual-binary pilot feature->pilot no thirdparty Third-party platform move->thirdparty needs policy/RBAC pilot->stay blocked pilot->move clean

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:

  1. Capture a baseline plan with your current Terraform version.
  2. Back up remote state out-of-band and record the serial number.
  3. Run tofu init against the existing working directory.
  4. Regenerate the lock file for all target platforms.
  5. Plan with OpenTofu and require an empty diff.
  6. Cut CI over to the tofu binary.
  7. Keep a tested rollback to Terraform until two clean apply cycles pass.
migration base Baseline plan (TF) backup Backup state base->backup init tofu init backup->init lock Regen lock file init->lock gate Plan empty? lock->gate ci Cut CI to tofu gate->ci yes rollback Rollback to TF gate->rollback no

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:

  1. 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.
  2. Run the registry availability check and a single-workspace tofu plan on 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.
  3. 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

Related Articles

Thorsteinn Halldorsson Senior Cloud Engineer

Senior Cloud Engineer with 25+ years of hands-on experience across the datacenter-to-cloud stack: fiber SAN and disk storage, IBM/Lenovo blade and Dell/HP/Lenovo servers, Hyper-V and VMware clusters, and SQL and Remote Desktop Services (RDS) clusters. Deep in the Microsoft platform — Active Directory, PKI/certificate services, SQL, Power BI, Dynamics 365 Business Central (NAV) and AX (Axapta), Microsoft 365, Entra, and Intune — with a focus on Azure operations, FinOps, and applying AI tools like GitHub Copilot and Claude in real workflows. Writes practical, no-nonsense guides for IT professionals who need to ship real solutions.

Leave a Reply

Your email address will not be published. Required fields are marked *