--- title: "Terraform vs OpenTofu in 2026, and how to migrate" canonical: "https://admiral.io/blog/terraform-vs-opentofu" description: "In 2026, how to choose Terraform or OpenTofu if you are starting out, if you froze an old version, or if you are still current." published: "2026-10-07" --- # Terraform vs OpenTofu in 2026, and how to migrate Terraform and OpenTofu both turn configuration into cloud infrastructure. In 2026 the question is which one you should choose. If you are a startup choosing for the first time, changing your mind later means a migration. If you pick Terraform today, you will most likely be on the latest release, and that narrows where you can run it. Some third-party platforms stop at 1.5.7, the last release under the old license, which can make HCP Terraform the main option that runs current Terraform for you. Its free tier covers up to 500 managed resources, and above that you pay per managed resource, so the cost climbs quickly as your infrastructure grows. For a new stack that is not tied to HCP Terraform, a procurement rule, or a vendor support contract, start with OpenTofu. The reason is ecosystem fit and open governance, not a gap in what Terraform can build. If you froze Terraform at 1.5.7 or 1.6, you are running a release that no longer gets security fixes. If you kept upgrading, OpenTofu publishes a migration guide for every Terraform line up to 1.9. The move from 1.5.7 needs no code changes. The guides for 1.6 through 1.9 list a short set of known differences, mostly S3 backend options, `removed` blocks, test overrides and a few functions, and each one comes with a fix. If you are on a currently supported Terraform release, which today means 1.15 or 1.16, the documented path runs out and the two products have started to drift. The `action` block and `terraform query`, added in Terraform 1.14, are in those releases, and as of October 2026 OpenTofu has no equivalent. We expect more of this, because OpenTofu cannot copy newer Terraform code and each side now builds its own features. The further the forks move apart, the more risk you carry and the more work a later migration takes. A stack that uses these features has more to unwind. A stack that does not can still move, but no guide covers it. ## How did we get here? On August 10, 2023, HashiCorp announced that future Terraform releases would move to the Business Source License 1.1. That license is source-available, not open source by the OSI definition. Competing vendors would no longer be able to use those releases in competing offerings, as HashiCorp defined them, while APIs, SDKs and most libraries stayed under MPL 2.0. Terraform 1.5.7 is the last MPL release and the BSL begins at 1.6.0. Each BSL release converts to MPL 2.0 four years after publication, so 1.6.0 converts around October 2027. Within weeks a community group published a [manifesto](https://opentofu.org/manifesto/) and forked the last MPL commit. The public OpenTofu repository went live on September 5, 2023, the Linux Foundation announced it would host the project on September 20, and OpenTofu 1.6 reached general availability on January 10, 2024. In April 2024 HashiCorp sent a cease-and-desist letter, which OpenTofu's counsel disputed, and no public outcome has been reported. IBM completed its acquisition of HashiCorp in February 2025 and the BSL stayed in place. The restriction fell on vendors hosting Terraform, not on a team using it for their own cloud. A company could keep upgrading. Hosted platforms that compete with HCP Terraform could not ship those releases, so they stopped at 1.5.7. ## What changed The project has shipped steadily. OpenTofu has released seven minor versions since 1.6, most recently 1.13 in September 2026, with security support through August 2027. The CNCF [accepted it into the Sandbox](https://www.cncf.io/projects/opentofu/) on April 23, 2025. Sandbox is the entry maturity level, so it has not reached Incubating. It also has people behind it. Companies pledged funded engineering staff for at least five years starting in 2023, and the [supporters page](https://opentofu.org/supporters/) now lists 163 companies, 12 projects, one foundation and about 791 individuals. The project is governed by a technical steering committee and hosted by the Linux Foundation and the CNCF, so no single vendor owns it. ## How OpenTofu and Terraform differ in 2026 Day-to-day HCL, providers, modules, backends and the core CLI are the same, and providers did not change license. The differences are in newer features, and matching version numbers do not mean matching behavior, because OpenTofu 1.11 and Terraform 1.11 are different things. The table lists the features that matter most as of October 2026, ordered by who shipped first. The comparison is OpenTofu 1.13 against Terraform 1.16, taken from the [OpenTofu changelog](https://github.com/opentofu/opentofu/blob/v1.13.0/CHANGELOG.md) and the [Terraform changelog](https://github.com/hashicorp/terraform/blob/v1.16.0/CHANGELOG.md). No equivalent means no equivalent in the open-source CLI for that release. | Capability | OpenTofu | Terraform | Shipped first | |---|---|---|---| | Client-side state and plan encryption | 1.7 | No equivalent | OpenTofu | | Provider-defined functions | 1.7 | 1.8 | OpenTofu | | Variables in backend and encryption config | 1.8 | No equivalent | OpenTofu | | Variables and locals in module `source` | 1.8 | 1.15 | OpenTofu | | Provider `for_each` | 1.9 | No equivalent | OpenTofu | | `-exclude` flag | 1.9 | No equivalent | OpenTofu | | OCI registry sourcing for modules and providers | 1.10 | No equivalent | OpenTofu | | `enabled` argument on resources and modules | 1.11 | No equivalent | OpenTofu | | `language` block | 1.12 | No equivalent | OpenTofu | | Ephemeral resources | 1.11 | 1.10 | Terraform | | Write-only attributes | 1.11 | 1.11 | Same release number | | `terraform stacks` (HCP Terraform only) | No equivalent | 1.13 | Terraform | | `action` blocks | No equivalent | 1.14 | Terraform | | List resources and `terraform query` | No equivalent | 1.14 | Terraform | OpenTofu shipped most of the language features first. Terraform shipped ephemeral resources a release earlier and now leads on actions and queries. Both tools now have a `const` argument on input variables, but the documented behavior differs. OpenTofu infers whether a variable is constant when you leave `const` unset, and `const = false` opts out. Terraform's documentation says `const` must be set before a variable can be used in module and provider `source` and `version` attributes. Setting `const = true` explicitly is the form both tools document. `terraform stacks` and Sentinel belong to HCP Terraform and Terraform Enterprise, so teams outside those products can ignore them. The `action` block and `terraform query` are core language and CLI features, so teams that never touch HCP will face them. If your delivery model is built around HCP Terraform, that is a legitimate reason to stay. ## Which migration guide matches your Terraform version If you run Terraform 1.5 through 1.9, OpenTofu publishes a [guide](https://opentofu.org/docs/intro/migration/) for your exact line, and 1.5.x is the easiest case. Each guide is exact to one patch release, so you may need to move to that patch first. The guides target older OpenTofu releases, so finish by upgrading to a supported release in a separate change. If you kept upgrading past 1.9, there is no versioned guide for your release. OpenTofu's [general migration guide](https://opentofu.org/docs/intro/migration/migration-guide/) walks through a backup, a plan, and an apply. Its page on [interdependent configurations](https://opentofu.org/docs/intro/migration/multiple-configurations/) says OpenTofu reads Terraform 1.x state, and that Terraform may not reliably read state OpenTofu wrote. Neither page is a guide for Terraform 1.15 or 1.16, so the validation is yours. Upgrade carefully and start with a trial run on a copy of your state. The next section covers how. ## How to migrate from Terraform to OpenTofu Migrate one stack at a time, starting with a low-stakes one. Each stack goes through the same four steps. 1. **Prepare.** Check what you are about to migrate. Note each stack and workspace, since each has its own state, plus the providers and modules they use and any `required_version` constraint, which can block `tofu`. Apply any pending changes and confirm `terraform plan` shows nothing. Make sure you can roll state back, either by turning on versioning for the state bucket or by saving a copy with `terraform state pull`. The pulled state, and any plan you save later, can contain secrets. Keep those files out of git and treat them like credentials. ``` terraform plan # expect: No changes terraform state pull > backup-pre-cutover.tfstate ``` 2. **Run a trial on a copy.** Copy the backup to a trial state file. ``` cp backup-pre-cutover.tfstate trial.tfstate ``` Then comment out the real `backend` block and add a local backend that points at the copy. This is a code change, not a command, and it keeps the trial away from the shared state and its lock. ```hcl terraform { # backend "" { ... } backend "local" { path = "trial.tfstate" } } ``` Run `tofu init -reconfigure`. Because you swapped backends, `init` will not continue without a flag, and `-reconfigure` points at the new backend without copying any state, so the trial uses the file you just made. Skip `-upgrade`, which also moves provider versions and clouds the diff. Expect `init` to rewrite the lock file, switching each provider from `registry.terraform.io` to `registry.opentofu.org`. It keeps the versions but not the hashes, because OpenTofu's provider releases are not byte-for-byte identical to HashiCorp's. Finish with `tofu plan`. A plan that reports no changes means this copy is readable and this configuration plans clean. It does not prove the real backend, the workspace you will cut over, or the production run. Any diff is a reason to stop and investigate. ``` tofu init -reconfigure tofu plan # expect: No changes ``` 3. **Cut over.** Up to here OpenTofu has only worked on a copy, and the shared state has not been written to. This step is the first time OpenTofu writes to that state, and the stack becomes OpenTofu-owned. Rolling back after that means restoring a backup or repairing the state, and it gets harder once a real change has been applied. [OpenTofu's migration guide](https://opentofu.org/docs/intro/migration/migration-guide/) calls the process reversible. The section on switching back says when that holds. Start by undoing the trial. Un-comment the original `backend` block and delete the local one, so the configuration is back to what it was. ```hcl terraform { backend "" { ... } } ``` Then point back at the real backend with `-reconfigure`. Never use `-migrate-state` here, because it could offer to copy the trial state over the real one. The commitment point is the first command that writes state. Apply is the intended one, because even with no changes it lets OpenTofu update the state file format if needed. Other commands that write state, including `state mv`, `state rm`, `state push` and `import`, make the stack OpenTofu-owned the same way. A saved plan keeps what you reviewed identical to what you apply. Commit the rewritten lock file with the cutover. ``` tofu init -reconfigure tofu plan -out=cutover.tfplan # expect: No changes tofu apply cutover.tfplan # only after a clean plan ``` 4. **Follow with a small change.** Make a small, non-critical change under OpenTofu. Then run read-only plans from both engines against the same commit for a couple of weeks and diff the results. That is the best canary. A clean plan in one workspace does not prove the others, so repeat steps 2 and 3 for each workspace. Update your tooling as you go. Switch one pipeline at a time, lowest stakes first, and keep a disabled fallback job. GitHub Actions has the community-maintained `opentofu/setup-opentofu` action. For other tools, look for an engine or runtime setting. ## After you migrate The lock file now lists `registry.opentofu.org` providers. The versions carry over and the hashes are new. ```hcl provider "registry.opentofu.org/hashicorp/time" { version = "0.14.2" constraints = ">= 0.14.2" hashes = [ "h1:0lkmuDlyUBEK2vAxb9r8jY8kMpqYbMoSb4GWnSbA9iY=", "h1:4ccOXW03+ENKJieXGwTfMvRlkpT9o+ra6dw240C1UFE=", # ... ] } ``` The `init` output still prints the short name, `hashicorp/time`. That is normal. ## Can you switch back from OpenTofu to Terraform? Going back is harder than going in, and it gets harder the longer you stay. OpenTofu's documentation calls the process reversible, and right after cutover, with the same features and no real changes, it is. Every apply, every new resource and every OpenTofu-only feature moves you further from that point. OpenTofu's migration pages cover one direction only, so your mileage will vary. Rollback has two cases. - **No real change has been applied since cutover.** Stop using OpenTofu, restore the state backup, run `terraform init` and `plan`, confirm nothing unexpected, and test a small change. - **Changes have been applied under OpenTofu.** Do not restore the backup. The backup is older than reality, so new resources become unmanaged and the next Terraform apply may try to recreate or destroy things. Inspect the current state before you point Terraform at it, and be ready to repair it by hand. OpenTofu records its own version in the state file after an apply, for example 1.13, so the state no longer looks like Terraform wrote it. Define rollback triggers up front, such as a destroy or replace on a resource that is unchanged in code, or an unclean plan on consecutive runs. Several things make the door one-way in practice. - **State and plan encryption.** The documentation does not say Terraform can read OpenTofu-encrypted state, so plan as though it cannot. [Encryption](https://opentofu.org/docs/language/state/encryption/) can be unwound within OpenTofu by adding an `unencrypted` method as primary, keeping the old method as `fallback`, turning off `enforced`, applying, and then removing the block. That does not help you return to Terraform. - **Language additions.** Provider `for_each`, the `enabled` argument, the `language` block and variables in backend blocks have no Terraform equivalent. Terraform has its own `const`, and provider-defined functions work in Terraform from 1.8, so those are not one-way items. - **`.tofu` files.** These are a different hazard. If `versions.tf` and `versions.tofu` both exist, OpenTofu ignores `versions.tf` and reads `versions.tofu`. It still reads the other `.tf` files. Terraform does not read `.tofu` files, so after a rollback it runs `versions.tf` and never sees `versions.tofu`. The behavior is in [OpenTofu's settings documentation](https://opentofu.org/docs/language/settings/). Avoid adding `.tofu` files until a return to Terraform is no longer a goal. - **Pipeline features.** The `-exclude` flag is a CLI flag, so it costs you a pipeline feature on rollback and nothing more. Keep Terraform installed and the old pipeline definitions on a branch for one full apply cycle, in case you need to roll back. Then remove both, so muscle memory does not send someone to run `terraform` against a stack that OpenTofu now owns. Adopt OpenTofu-only features deliberately, not during the migration window. In particular, do not turn on state encryption during migration, because it stacks a second risky change onto an already risky one. ## What breaks during a Terraform to OpenTofu migration - **`required_version` constraints.** OpenTofu changed how it reads these. Through 1.11 it compared any `required_version` against its own version number, so a constraint such as `~> 1.5.0` or `< 1.6` rejected `tofu`. From 1.12, [OpenTofu's settings documentation](https://opentofu.org/docs/language/settings/) says a `required_version` in a plain `.tf` file is ignored, and constraints are enforced only in `.tofu` files or in a `language` block with `compatible_with`. Check which OpenTofu release you pin, search every root and shared module, and decide a policy before cutover. - **Accidental Terraform runs.** Reading Terraform's source code, a stray `terraform plan` on a migrated stack should stop at the lock file, which now lists `registry.opentofu.org` providers, and report an inconsistent dependency lock file. Do not count on the state to protect you. Current Terraform no longer compares the `terraform_version` recorded in state with its own version, so a state last written by OpenTofu is accepted. A plan still takes the state lock, so it can block a real run. `terraform init` rewrites the lock file and removes that protection, and `terraform apply` rewrites state with Terraform's version and provider addresses. Add a CI check on the engine in use, and tell people not to run `terraform init` on a migrated stack. - **One engine owns a stack at a time.** Freeze merges to the stack during its cutover window and gate the old pipeline, so a Terraform job cannot fire against state OpenTofu just wrote. - **Registry addresses and missing providers.** Fully qualified `registry.terraform.io/...` sources should become short forms, for providers and modules alike. Niche, vendor-specific and private providers and modules may be absent from the OpenTofu registry. Symptoms include failures to query available provider packages and module-not-found errors. Confirm the code works on Terraform, file a registry request, or use an explicit registry.terraform.io source. - **Lock file churn.** Hashes and source addresses change. Use `tofu providers lock` with a `-platform=` flag for each platform your developers and CI use, and commit the lock file with the engine swap. OpenTofu 1.12 records checksums for all supported platforms on `init`. - **The `cloud` block and HCP Terraform.** Since OpenTofu 1.6 the `cloud` and `remote` backends no longer default to app.terraform.io, so set `hostname` (or `TF_CLOUD_HOSTNAME`). OpenTofu's documentation does not promise compatibility with HCP Terraform, Terraform Enterprise or Sentinel, so those teams need a different plan, and policy as code moves to OPA or platform tooling. - **Hardcoded binaries, tooling and policies.** Dockerfiles, scripts, linters, scanners and policy engines often call `terraform` or assume `.tf` files. Search for it and test each tool against `tofu` and `.tofu`. In a mixed fleet, policies keyed to "version 1.x" become ambiguous, so log the engine too. - **Remote state consumers.** OpenTofu's page on interdependent configurations says to migrate consumers before the stacks they read from, so producers go last. OpenTofu reads Terraform state, but Terraform may not reliably read OpenTofu state. If you enable encryption later, consumers also need matching `remote_state_data_sources` settings, so configure encryption keys and those settings before dependent stacks run. ## Where to go from here The right choice depends on your team. If Terraform fits how you deliver today, because of HCP Terraform, procurement rules or a vendor support contract, staying is a sound decision. Just keep in mind that the gap between the two tools widens with every release, so this is a good time to look at your Terraform strategy on purpose and not by default. If you want to see where you stand, pick one low-stakes workspace and run the trial from step 2. It touches nothing shared, and the plan tells you how much of this post applies to you. If something blocks you, such as a missing provider, a pipeline tool that cannot run against OpenTofu, or a diff you cannot explain, you will have found it before committing anything. If the plan comes back clean, the copy is readable. The cutover is still a separate step. We hope this helps you make the call that fits your team. Whichever engine you choose, the next challenge is keeping infrastructure and application changes in step, which is the problem we built Admiral to solve. Admiral is an orchestrator. It brings infrastructure and workload changes into one workflow, and you keep your own tools. The work itself runs on agents in your own environment, a deliberate part of our security approach.