Add HashiCorp’s Fedora repository on Fedora 45 and dnf install terraform ends with “No match for argument: terraform”. The repository file works, but HashiCorp builds a tree per Fedora release, and at the time of testing there was a Fedora 44 tree and no Fedora 45 one. OpenTofu, the open-source fork, has the opposite problem. Fedora packages it, but that package has missed several upstream security releases, and OpenTofu’s own installer quietly gives you the same stale Fedora build because its repo file points at a CA bundle path Fedora no longer ships.
Below you’ll install Terraform from HashiCorp’s repository on Fedora 45 and 44, with the workaround for when HashiCorp has no tree for your release yet, and OpenTofu from its own repository with the CA fix. Then you’ll run the same configuration under both tools, try the Terraform 1.17 changes that matter on a fresh install, and see where the two part ways, from the provider registry to OpenTofu’s state encryption. Every command below was re-run on fresh Fedora 45 and 44 VMs in late October 2026 with Terraform 1.17.0 and OpenTofu 1.13.2, the OpenTofu and 1.17 steps on the Fedora 44 one.
Choose Terraform or OpenTofu
Both read the same HCL and drive the same providers, so the choice is mostly about licence and source rather than syntax. This is what the two looked like on our Fedora boxes:
| Terraform | OpenTofu | |
|---|---|---|
| Project licence (RPM metadata) | BUSL-1.1 | MPL-2.0 (upstream tofu RPM) |
| Package source | HashiCorp repo only | Fedora repos, or OpenTofu’s repo |
| Version we installed | 1.17.0 | 1.13.2 (OpenTofu repo); Fedora ships 1.12.3 (45) and 1.11.5 (44) |
| Provider registry | registry.terraform.io | registry.opentofu.org |
| Built-in state encryption | No | Yes |
| Command | terraform | tofu |
Licence aside, both work for running your own infrastructure. The Business Source License lets you use Terraform for that, and restricts building a competing product on it, so most teams are not affected by it directly. If your team already runs Terraform with HCP Terraform or Sentinel policies, stay on it. For new work on Fedora we would pick OpenTofu from OpenTofu’s own repository: encrypted state alone is worth it whenever state lives in a shared bucket, and the project ships security fixes faster than Fedora’s package has been picking them up. Nothing stops you installing both, which is how we compared them. If you are picking one to learn properly, our roundup of Terraform and OpenTofu books covers material that applies to either.
Install Terraform from the HashiCorp repository
Add HashiCorp’s repository with dnf5’s addrepo and install. On Fedora 44 that is all it takes, and dnf picks the 1.17 build even though HashiCorp shipped a 1.16 patch release a day later into the same tree:
sudo dnf config-manager addrepo --from-repofile=https://rpm.releases.hashicorp.com/fedora/hashicorp.repo
sudo dnf install -y terraform
On the first install dnf imports HashiCorp’s signing key, so check what you are trusting. The key at https://rpm.releases.hashicorp.com/gpg was a new one, created on 9 September 2026:
curl -s https://rpm.releases.hashicorp.com/gpg | gpg --show-keys --with-fingerprint
Your output should match the fingerprint on HashiCorp’s security page, which also notes that the previous key, ending A621 E701, was used until 10 September 2026:
pub rsa4096 2026-09-09 [SC] [expires: 2031-09-08]
D55C 0D1A C78A 8D81 26CB 631C FC9C A96A CA02 6560
uid HashiCorp Security (HashiCorp Package Signing) <[email protected]>
The key ID dnf printed during the install, 0xCA026560, is the last eight digits of that fingerprint, so the package you just installed was signed with the new key.
Error: “No match for argument: terraform” on Fedora 45
HashiCorp’s repository file builds its URL from $releasever, so on Fedora 45 dnf asks for /fedora/45/. HashiCorp publishes a tree per release, and when we checked again on 24 October 2026 the 45 tree still did not exist, while the 44 tree had last been rebuilt on 23 October with 1.17.0 in it. Without a tree for your release, dnf cannot load the repository and the install fails like this:
>>> Usable URL not found
Repositories loaded.
Failed to resolve the transaction:
No match for argument: terraform
You can try to add to command line:
--skip-unavailable to skip unavailable packages
Check which trees exist before you change anything. This prints 200 once HashiCorp has published your release and 404 while it has not; on our Fedora 45 box it returned 404, while the same URL for 44 returned 200:
curl -s -o /dev/null -w "%{http_code}\n" https://rpm.releases.hashicorp.com/fedora/$(rpm -E %fedora)/$(uname -m)/stable/repodata/repomd.xml
Terraform ships as a single Go binary, and the Fedora 44 build installed and ran (terraform version) on 45 in our test. Point both HashiCorp repositories at the 44 tree. dnf5 stores this in its override file and leaves HashiCorp’s repo file untouched:
sudo dnf config-manager setopt hashicorp.baseurl=https://rpm.releases.hashicorp.com/fedora/44/\$basearch/stable hashicorp-test.baseurl=https://rpm.releases.hashicorp.com/fedora/44/\$basearch/test
sudo dnf install -y terraform
terraform version
Both releases ended up with the same build, installed from the 44 tree:
Terraform v1.17.0
on linux_amd64
Undo the pin once the check above returns 200 for 45, so you stop riding on the 44 tree. We tested the removal; it leaves the override file without the HashiCorp entries:
sudo dnf config-manager unsetopt hashicorp.baseurl hashicorp-test.baseurl
The capture below is the whole sequence on our Fedora 45 box, from the failure to the working install:

More on dnf5’s override file, and why a repo can come back with settings you removed, is in our dnf5 config-manager guide.
Why not Fedora’s OpenTofu package
Fedora ships OpenTofu as opentofu, built and signed by the Fedora project, and it installs like any other package. If you only want upstream’s build, skip this command and go straight to the next section:
sudo dnf install -y opentofu
tofu version
On Fedora 45 that gave us 1.12.3.
OpenTofu v1.12.3
on linux_amd64
Fedora 44 had 1.11.5. Both are stale on their own branches, not just a minor release behind. Fedora’s last opentofu updates went stable in February (1.11.5) and July 2026 (1.12.3), according to Fedora’s update system. Upstream has since released 1.12.4 through 1.12.9, and five of those six carry security advisories; the 1.11 branch has had at least five security releases since 1.11.5. Until Fedora catches up, install from OpenTofu’s repository, which is the next section. If you already installed opentofu, the swap there replaces it.
Install OpenTofu from its repository
OpenTofu’s installer script adds its repository and installs from it. On Fedora 44 the script reported success and printed a version, but the version was Fedora’s 1.11.5, not upstream’s current release:
curl -fsSL https://get.opentofu.org/install-opentofu.sh -o install-opentofu.sh
chmod +x install-opentofu.sh
sudo ./install-opentofu.sh --install-method rpm
Look at the repository file it writes, /etc/yum.repos.d/opentofu.repo. It sets sslcacert=/etc/pki/tls/certs/ca-bundle.crt. Fedora 44 and 45 no longer have that file. The Fedora change that dropped it recommends /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem for anything that needs an explicit file, while Rocky Linux 10 still keeps the old path as a symlink. So every request to OpenTofu’s repository fails, dnf carries on with the repositories that work, and Fedora’s opentofu package wins. Asking for the repository directly, as in sudo dnf swap -y opentofu tofu --from-repo=opentofu, shows the real error:
>>> Curl error (77): Problem with the SSL CA cert (path? access rights?) for https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/SRPMS/repodata/repomd.xml [error adding trust anchors from file: /etc/pki/tls/certs/ca-bundle.crt] - https://packages.opentofu.org/opentofu/tofu/rpm_any/rpm_any/SRPMS/repodata/repomd.xml
Point both OpenTofu repositories at Fedora’s bundle with a dnf5 override, then swap the Fedora package for upstream’s tofu. A swap is needed because both packages own /usr/bin/tofu:
sudo dnf config-manager setopt opentofu.sslcacert=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem opentofu-source.sslcacert=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
sudo dnf swap -y opentofu tofu
tofu version
On the way, dnf imports OpenTofu’s key (0xE5FD9F80, the same key that signs the providers tofu init downloads later) and the repository key of packagecloud, which hosts the repository (0x4C61499F). The result is the upstream build:
OpenTofu v1.13.2
on linux_amd64
You can apply the same sslcacert fix to any third-party repository file that hardcodes the old bundle path; if a vendor repo on Fedora 44 or 45 fails with curl error 77, check that line first. If you would rather not run a downloaded script as root, the OpenTofu RPM install page lists the repository definition to write by hand, and you can set the right sslcacert from the start.
On our Fedora 44 box the whole fix ran like this, from the curl error to the upstream package:

After the swap, rpm -q opentofu reports the Fedora package as gone, and future updates come from OpenTofu’s repository.
Run the same configuration with both
A small configuration with the random and local providers shows how far the two tools still agree. Create a project directory and the file:
mkdir -p ~/iac-demo && cd ~/iac-demo
vim main.tf
Paste in the file we ran with both tools.
terraform {
required_providers {
random = {
source = "hashicorp/random"
}
local = {
source = "hashicorp/local"
}
}
}
resource "random_pet" "name" {
length = 2
}
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}\n"
}
output "pet" {
value = random_pet.name.id
}
Initialise and apply with OpenTofu.
tofu init
tofu apply -auto-approve
You should see the providers arrive signed by OpenTofu’s registry key and the apply create both resources.
- Installed hashicorp/random v3.9.1 (signed, key ID 0C0AF313E5FD9F80)
- Installed hashicorp/local v2.9.1 (signed, key ID 0C0AF313E5FD9F80)
OpenTofu has been successfully initialized!
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
pet = "real-leech"
Terraform needs its own directory, or both tools write into one lock file. Copy the file across and run the same two steps with terraform; the 1.17 section below builds on the state this apply leaves behind:
mkdir -p ~/tf-demo && cp ~/iac-demo/main.tf ~/tf-demo/ && cd ~/tf-demo
terraform init
terraform apply -auto-approve
Terraform installed the same provider versions, “signed by HashiCorp” this time, and created its own pet:
- Installed hashicorp/random v3.9.1 (signed by HashiCorp)
- Installed hashicorp/local v2.9.1 (signed by HashiCorp)
Terraform has been successfully initialized!
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
pet = "simple-duckling"
The difference shows up in .terraform.lock.hcl: OpenTofu records registry.opentofu.org/hashicorp/random, Terraform records registry.terraform.io/hashicorp/random. Commit the lock file for the tool your team actually uses, and plan on a fresh init when you switch, since the entries name a different registry. Our Terraform and OpenTofu guide for Ubuntu 26.04 covers the same pair on Debian-family systems, and once either tool works, the same providers drive real infrastructure, such as VMs on KVM or instances on Hetzner Cloud.
Try the Terraform 1.17 changes
HashiCorp shipped Terraform 1.17 on 21 October 2026, so anyone installing from the repository today gets it. Most of the 1.17 changelog will not touch a first project, but four items will, and we ran each one on the Fedora 44 box, starting from the ~/tf-demo state built above. Terraform Policy also went generally available behind the -policies flag; we did not test it here.
Read the version as JSON
If a CI job or a wrapper script checks the Terraform version, it probably parses the JSON form. We ran it right after the install, outside any project, which is why provider_selections comes back empty; inside an initialised directory it lists the providers:
terraform version -json
The output now opens with format_version:
{
"format_version": "1.0",
"terraform_version": "1.17.0",
"platform": "linux_amd64",
"provider_selections": {},
"terraform_outdated": false
}
A parser that reads terraform_version and ignores unknown keys keeps working. HashiCorp added the field so it can change this output later, so a script you write now should check format_version before trusting the rest.
Plan with -minimal-refresh
A normal plan refreshes every resource in state before it compares anything, and on a large state against a slow cloud API that refresh is most of the wait. To see the difference, change the greeting in main.tf so only local_file.hello has a change, then plan once the usual way:
cd ~/tf-demo
sed -i "s/Hello from/Hi from/" main.tf
terraform plan
Both resources get refreshed, including the random_pet nothing has touched:
random_pet.name: Refreshing state... [id=simple-duckling]
local_file.hello: Refreshing state... [id=73de623923c183ecc990f8e1ff3bb1c96d84ec75]
Plan: 1 to add, 0 to change, 1 to destroy.
Now run the same plan with the new flag. The configuration and state haven’t changed since the last plan, so any difference in the output comes from the refresh step alone:
terraform plan -minimal-refresh
Only the file with a pending change was read back, and the plan itself is identical:
local_file.hello: Refreshing state... [id=73de623923c183ecc990f8e1ff3bb1c96d84ec75]
Plan: 1 to add, 0 to change, 1 to destroy.
The price is drift detection. terraform plan -help says the flag plans “against a stale record of the remote system state” for anything it skips, so a change someone made by hand in the console goes unnoticed. Use it for quick iterations on a big state, and keep a full plan in CI and before every apply that matters.
Use a variable in required_providers
Until 1.17, a provider’s source and version had to be literal strings. Now they can come from locals and variables, which helps when one module tree has to pin different provider majors per environment. Try it in a new directory:
mkdir -p ~/tf-pin && cd ~/tf-pin
vim main.tf
This is the file we ran. The variable carries const = true, and that line is required:
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = local.random_version
}
}
}
variable "random_major" {
type = string
default = "3"
const = true
}
locals {
random_version = "~> ${var.random_major}.0"
}
resource "random_id" "suffix" {
byte_length = 4
}
Initialise it with the default, then again with the major set to 2 from the command line:
terraform init
rm -rf .terraform .terraform.lock.hcl
terraform init -var random_major=2
The constraint follows the variable each time, so the same directory resolved a different provider major without a single edit to main.tf:
- Finding hashicorp/random versions matching "~> 3.0"...
- Installed hashicorp/random v3.9.1 (signed by HashiCorp)
Terraform has been successfully initialized!
- Finding hashicorp/random versions matching "~> 2.0"...
- Installed hashicorp/random v2.3.1 (signed by HashiCorp)
Terraform has been successfully initialized!
Every later command needs the same value. A bare terraform plan after that second init fell back to the default of 3 and refused to run, because the lock file now records 2.3.1:
Error: Inconsistent dependency lock file
The following dependency selections recorded in the lock file are
inconsistent with the current configuration:
- provider registry.terraform.io/hashicorp/random: locked version selection 2.3.1 doesn't match the updated version constraints "~> 3.0"
Exporting the value once keeps init, plan and apply in step, and with it set the plan went through (Plan: 1 to add):
export TF_VAR_random_major=2
terraform plan
This file is Terraform-only for now. The current OpenTofu release rejected it at tofu init with Error: Invalid version constraint and “Version must be specified as a string.”, so keep literal versions in any module both tools have to read.
Error: “Only literal values and const variables can be evaluated during init”
Leave out const = true and terraform init stops before it downloads anything:
Error: Unknown provider version
on main.tf line 5, in terraform:
5: version = local.random_version
Only literal values and const variables can be evaluated during init.
The error points at the local, but the fix goes on the variable the local reads. A const variable has to be known before init runs, from a default, -var, a .tfvars file or a TF_VAR_ environment variable, so it cannot depend on a resource or a data source.
log() and pow() now fail on infinity
Before 1.17, log and pow handed back an infinite value, and a result that was not a number at all could crash Terraform. Both now stop with an error, which you can check from the console:
echo "log(0, 10)" | terraform console
echo "pow(10, 400)" | terraform console
Each call fails the same way. This is the log one, and pow differs only in the function name:
Error: Error in function call
on <console-input> line 1:
(source code not available)
Call to function "log" failed: result is not a number.
OpenTofu still returns -Inf and +Inf for the same two expressions, so a sizing formula that worked under tofu can fail under terraform. Guard the input, for example with max(var.n, 1) before log. The other behaviour change applies only to configurations with ephemeral resources: Terraform now shows the diagnostics raised when it renews one, so new warnings can appear on a configuration that ran clean under 1.16.
Encrypt OpenTofu state
State files hold every attribute of every resource, including generated passwords and keys, in plain JSON. Our unencrypted demo state contained the pet name three times. OpenTofu can encrypt state with a passphrase or a cloud KMS key, configured in the terraform block. Start in a fresh directory: OpenTofu refuses to read an existing plaintext state once encryption is configured, unless you add a migration fallback, and ~/iac-demo already has one. We copied the demo and added a second file:
mkdir -p ~/iac-enc && cp ~/iac-demo/main.tf ~/iac-enc/ && cd ~/iac-enc
vim encryption.tf
Read the passphrase from a variable, so it never sits in the file. We ran this configuration.
terraform {
encryption {
key_provider "pbkdf2" "main" {
passphrase = var.state_passphrase
}
method "aes_gcm" "main" {
keys = key_provider.pbkdf2.main
}
state {
method = method.aes_gcm.main
}
}
}
variable "state_passphrase" {
type = string
sensitive = true
}
Pass the passphrase through the environment and apply. The pbkdf2 provider wants at least 16 characters. We exported a literal in the lab, as below. On a real machine, type it with read -rs TF_VAR_state_passphrase and then run a bare export TF_VAR_state_passphrase instead of the first line, which keeps it out of your shell history:
export TF_VAR_state_passphrase="correct-horse-battery-staple"
tofu init
tofu apply -auto-approve
Afterwards the state file carries only metadata and an encrypted_data field, and a search for the new pet name finds nothing. Reading it back works only with the passphrase, and we checked the three ways it fails:
- passphrase unset, with
-input=false:Error: Unable to compute static value(an interactive run prompts for it instead) - wrong passphrase:
Error: Error acquiring the state lock, withfailed to write backup file: decryption failed for allunderneath terraform show:Failed to read the given file as a state or plan file
Encrypted state ties the project to OpenTofu until you decrypt it again, which the docs cover as a rollback with the old method as a fallback. Losing the passphrase loses the state, so keep it in a secret manager. With the right passphrase, tofu output -raw pet returned touching-bluegill as normal. To encrypt an existing state instead of a new one, OpenTofu’s state encryption docs describe the fallback block that reads the old unencrypted file once. For state in an S3 or GCS bucket, swap the pbkdf2 key provider for the matching KMS one, so no passphrase has to live on developer laptops at all.