Installing Terraform takes about a minute. Setting it up so it doesn’t annoy you three weeks later takes a little longer. Which copy is actually on your PATH? Why does every project re-download the same large provider? Can you run two Terraform versions on one machine without breaking either?
This guide covers the whole path: install on Ubuntu, RHEL-family distros, Windows and macOS, check the binary, tune the CLI config, set up VS Code, switch versions with tfenv, use environment variables, turn on tab completion and upgrade without surprises. The Linux output below is from Ubuntu 26.04.1 LTS and AlmaLinux 10.2 running Terraform 1.16.6. The Windows output is from a real Windows 11 machine. I couldn’t test macOS here, so those commands come straight from HashiCorp’s documentation.
Which install method fits your system
| System | Install with | Upgrade with |
|---|---|---|
| Ubuntu, Debian | HashiCorp apt repository | sudo apt install --only-upgrade terraform |
| RHEL, Rocky, AlmaLinux | HashiCorp dnf repository | sudo dnf upgrade terraform |
| Windows | winget or Chocolatey | winget upgrade Hashicorp.Terraform |
| macOS | Homebrew with HashiCorp’s tap | brew upgrade hashicorp/tap/terraform |
| Anything else | Zip from releases.hashicorp.com | Download the newer zip |
Use the repository whenever you can. You get signed packages and upgrades through the normal update flow, which beats a binary you downloaded once and forgot about.
Install Terraform on Ubuntu
Add HashiCorp’s key and repository, then install:
sudo apt update && sudo apt install -y gnupg curl lsb-release
wget -qO- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y terraform
That $(lsb_release -cs) bit fills in your release codename. On Ubuntu 26.04 it’s resolute, and the repo has packages for it. Want an older build instead of the newest? List what’s available and pin it:
apt-cache madison terraform | head -3
terraform | 1.16.6-1 | https://apt.releases.hashicorp.com resolute/main amd64 Packages
terraform | 1.16.5-1 | https://apt.releases.hashicorp.com resolute/main amd64 Packages
terraform | 1.16.4-1 | https://apt.releases.hashicorp.com resolute/main amd64 Packages
sudo apt install -y terraform=1.16.4-1
Install Terraform on RHEL, Rocky Linux and other distributions
Same idea with dnf. This works on RHEL, Rocky and AlmaLinux:
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y terraform
HashiCorp publishes the same kind of repo file for Fedora (/fedora/hashicorp.repo) and Amazon Linux (/AmazonLinux/hashicorp.repo). To see what versions the repo offers:
dnf list --showduplicates terraform | tail -3
terraform.x86_64 1.16.4-1 hashicorp
terraform.x86_64 1.16.5-1 hashicorp
terraform.x86_64 1.16.6-1 hashicorp
On a distro with no HashiCorp repo, the zip works everywhere. It’s one static binary:
curl -fsSLO https://releases.hashicorp.com/terraform/1.16.6/terraform_1.16.6_linux_amd64.zip
unzip terraform_1.16.6_linux_amd64.zip
sudo install -m 0755 terraform /usr/local/bin/terraform
Install Terraform on Windows with PowerShell and Git Bash
The easiest route is winget, which ships with current Windows 11:
winget install --id Hashicorp.Terraform -e
Prefer Chocolatey? choco install terraform does the same job from an admin shell. No package manager at all? Do it by hand:
Invoke-WebRequest https://releases.hashicorp.com/terraform/1.16.6/terraform_1.16.6_windows_amd64.zip -OutFile terraform.zip
Expand-Archive terraform.zip -DestinationPath C:\tools\terraform
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\tools\terraform", "User")
Open a new terminal afterwards. An already-open window keeps the old PATH, and that’s the number one reason people think the install failed.
Git Bash reads the Windows PATH, so it finds Terraform with no extra work. If you used the manual route and it doesn’t, add the folder in ~/.bashrc using the Unix-style path: export PATH="$PATH:/c/tools/terraform". This is what a winget install looks like from both shells:
PS> terraform version
Terraform v1.16.2
on windows_amd64
PS> (Get-Command terraform).Source
C:\Users\you\AppData\Local\Microsoft\WinGet\Packages\Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe\terraform.exe
type -a terraform # Git Bash
terraform is /c/Users/you/AppData/Local/Microsoft/WinGet/Packages/Hashicorp.Terraform_Microsoft.Winget.Source_8wekyb3d8bbwe/terraform
Install Terraform on macOS with Homebrew
Use HashiCorp’s own tap. The plain brew install terraform formula can be stuck on an old release.
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Works on Intel and Apple silicon. Upgrades later are brew upgrade hashicorp/tap/terraform.
Verify the Terraform version and PATH
Three commands tell you most of what you need:
terraform version
Terraform v1.16.4
on linux_amd64
Your version of Terraform is out of date! The latest version
is 1.16.6. You can update by downloading from https://developer.hashicorp.com/terraform/install
which terraform
/usr/bin/terraform
type -a terraform
terraform is /usr/bin/terraform
terraform is /bin/terraform
(That “out of date” notice is real output from a deliberately older install. It’s handy, so keep it in mind for the upgrade section.) /bin is just a symlink to /usr/bin on modern distros, so that’s one copy, not two.
If type -a lists two different paths, the first one wins. That’s the usual cause of “I upgraded Terraform but the version didn’t change”. Run hash -r after installing or removing a binary so Bash forgets the old location. On Windows, Get-Command terraform -All does the same job.
Configure the Terraform CLI: credentials and plugin cache
The CLI reads one optional file: ~/.terraformrc on Linux and macOS, %APPDATA%\terraform.rc on Windows. Point TF_CLI_CONFIG_FILE at a different path if you want it somewhere else. This one does three useful things:
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"
disable_checkpoint = true
credentials "app.terraform.io" {
token = "REPLACE_WITH_YOUR_API_TOKEN"
}
Create the cache folder first (mkdir -p ~/.terraform.d/plugin-cache) and lock the file down with chmod 600 ~/.terraformrc, because it holds a token. Here’s what each piece does.
Plugin cache. Without it, every project downloads its own copy of each provider. With it, providers are shared. There’s a catch, though. Terraform only reuses a cached provider when your lock file agrees with it. A brand new folder with no lock file downloads again. Copy the lock file in, or better, commit .terraform.lock.hcl to git, and it works:
cd ~/tf-b && terraform init
- Reusing previous version of hashicorp/random from the dependency lock file
- Using hashicorp/random v3.9.1 from the shared cache directory
Credentials. Only needed for HCP Terraform or Terraform Enterprise. Run terraform login and it opens a browser, then stores the token in ~/.terraform.d/credentials.tfrc.json. For CI, skip files entirely and set an environment variable: TF_TOKEN_app_terraform_io, with the dots in the hostname swapped for underscores. Cloud credentials for AWS, Azure or Google don’t go here. Those belong to each provider, usually through its own environment variables.
disable_checkpoint. Stops Terraform phoning home to check for new versions. Handy on locked-down servers. It also hides the “out of date” notice from the last section, so you trade one for the other.
Set up the VS Code Terraform extension and language server
Install HashiCorp’s extension from the Extensions panel (search “HashiCorp Terraform”) or from a terminal:
code --install-extension hashicorp.terraform
The language server (terraform-ls) comes bundled, so there’s nothing else to install. You do need VS Code 1.86 or newer and Terraform on your PATH. Open a folder with .tf files and the Terraform label shows up in the status bar. Run terraform init in that folder so completion knows about your providers. Then add format-on-save to your settings.json:
"[terraform]": {
"editor.defaultFormatter": "hashicorp.terraform",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"[terraform-vars]": {
"editor.defaultFormatter": "hashicorp.terraform",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
}
Working on a remote Linux box? Connect with the Remote – SSH extension and install the Terraform extension on the remote side, so it uses that machine’s Terraform.
Manage multiple Terraform versions with tfenv
One client pinned to 1.14, another on 1.16. Reinstalling the package every time you switch is miserable. tfenv fixes that. It needs git and unzip (install them first on a minimal RHEL-family box):
git clone --depth=1 https://github.com/tfutils/tfenv.git ~/.tfenv
echo 'export PATH="$HOME/.tfenv/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
tfenv --version
tfenv 3.2.2
tfenv list-remote | grep ^1.14 | head -3
1.14.9
1.14.8
1.14.7
tfenv install 1.14.9
tfenv: SHA256 hash matched!
tfenv: Installation of terraform v1.14.9 successful.
tfenv install 1.16.6
tfenv use 1.16.6
tfenv list
* 1.16.6 (amd64) (set by /home/asif/.tfenv/version)
1.14.9 (amd64)
The real trick is the per-project pin. Drop a .terraform-version file in a project and tfenv uses that version whenever you’re in the folder:
cd ~/tf-b
echo 1.14.9 > .terraform-version
terraform version
Terraform v1.14.9
on linux_amd64
Order of priority, highest first: the TFENV_TERRAFORM_VERSION variable, then .terraform-version, then your tfenv use default. Commit the file so your team and your CI get the same version. And watch your PATH. tfenv has to come first, or the packaged /usr/bin/terraform wins. Check with which -a terraform. On macOS, brew install tfenv works, but unlink the Homebrew Terraform first. On Windows, look at tfswitch or tenv instead, since tfenv is built for Unix shells.
Configure environment variables and shell completion
You’ll meet these constantly. All of them are optional.
| Variable | What it does |
|---|---|
TF_VAR_name |
Sets the input variable name without a tfvars file |
TF_LOG |
Turns on logging: TRACE, DEBUG, INFO, WARN or ERROR |
TF_LOG_PATH |
Writes those logs to a file instead of the terminal |
TF_INPUT=0 |
Never prompt, fail instead. Good for scripts |
TF_IN_AUTOMATION=1 |
Trims the “next steps” hints from output in CI |
TF_CLI_ARGS_plan |
Extra flags added to every terraform plan |
TF_DATA_DIR |
Moves the .terraform working directory |
TF_PLUGIN_CACHE_DIR |
Plugin cache location, if you’d rather not use the config file |
A quick test of TF_VAR_ using a variable named greeting that defaults to “hello”:
echo var.greeting | terraform console
"hello"
echo var.greeting | TF_VAR_greeting=howdy terraform console
"howdy"
To make a variable stick, put the export line in ~/.bashrc. The details of that file versus .bash_profile are covered in Bash environment variables and .bashrc vs .bash_profile.
Tab completion is one command, and it works for Bash and zsh:
terraform -install-autocomplete
tail -1 ~/.bashrc
complete -C /usr/bin/terraform terraform
Open a new shell and terraform pla<Tab> becomes terraform plan. Run the installer a second time and it refuses with “already installed”, which is fine. -uninstall-autocomplete reverses it.
Upgrade Terraform with compatibility checks
Upgrading the binary is the easy part:
sudo apt install --only-upgrade terraform
Unpacking terraform (1.16.6-1) over (1.16.4-1) ...
Setting up terraform (1.16.6-1) ...
sudo dnf upgrade -y terraform
Upgrading : terraform-1.16.6-1.x86_64
Cleanup : terraform-1.16.4-1.x86_64
Doing it safely takes a few more steps:
- Check your constraints first. If a project sets
required_version, Terraform won’t run outside it. This is what you get when a config says~> 1.14.0and you’re on 1.16.6:Error: Unsupported Terraform Core version on main.tf line 2, in terraform: 2: required_version = "~> 1.14.0" This configuration does not support Terraform version 1.16.6. To proceed, either choose another supported Terraform version or update this version constraint. - Read the release notes for the versions you’re skipping.
- Plan before and after. On the old version, run
terraform planand make sure it’s clean. Upgrade, run it again, and it should still say “No changes”. Anything else deserves a look before you apply. - Refresh providers deliberately.
terraform init -upgrademoves providers to the newest versions your constraints allow and rewrites.terraform.lock.hcl. Review that diff in git. - Treat apply as one way. Once a newer version writes your state, assume the older one can’t be trusted with it. Upgrade everyone and CI together, and pin with
.terraform-versionin the meantime.
To stop a routine apt upgrade from moving Terraform without you noticing:
sudo apt-mark hold terraform
terraform set on hold.
Release it later with sudo apt-mark unhold terraform. On the dnf side, the versionlock plugin does the same job.
Setup confirmation
Time to prove it all works. Each VM ran the same checks: version, path, CLI config, a real init and apply using the plugin cache, validation, formatting, tfenv and tab completion. Terraform is a command line tool with no web page, so there’s no screenshot here, just the terminal.
Ubuntu 26.04.1 LTS:
terraform version
Terraform v1.16.6
on linux_amd64
which terraform
/usr/bin/terraform
terraform init && terraform apply -auto-approve
- Using hashicorp/random v3.9.1 from the shared cache directory
Terraform has been successfully initialized!
random_pet.name: Creation complete after 0s [id=awake-reptile]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
terraform validate
Success! The configuration is valid.
terraform fmt -check && echo formatted
formatted
tfenv list
* 1.16.6 (amd64) (set by /home/asif/.tfenv/version)
1.14.9 (amd64)
bash -ic "complete -p terraform"
complete -C '/usr/bin/terraform' terraform
AlmaLinux 10.2:
terraform version
Terraform v1.16.6
on linux_amd64
which terraform
/usr/bin/terraform
terraform init && terraform apply -auto-approve
- Using hashicorp/random v3.9.1 from the shared cache directory
Terraform has been successfully initialized!
random_pet.name: Creation complete after 0s [id=united-husky]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
terraform validate
Success! The configuration is valid.
tfenv list
* 1.16.6 (amd64) (set by /home/asif/.tfenv/version)
1.14.9 (amd64)
bash -ic "complete -p terraform"
complete -C '/usr/bin/terraform' terraform
Same version, same binary location, cache reused, tfenv switching, completion registered. If your output looks like this, you’re set.
Frequently asked questions
Is Terraform free to install and use?
Yes. The CLI is free to download and run. It’s under HashiCorp’s Business Source License now, which matters mostly if you plan to build a competing product. OpenTofu is the open-source fork if that licence is a problem for you.
Why does terraform say “command not found” right after installing?
Almost always PATH. On Linux, run hash -r or open a new shell. On Windows, open a new terminal window, because existing ones keep the old PATH.
Should I install Terraform with a package manager or tfenv?
One machine, one project, one version: the package manager is simpler and upgrades through your normal updates. Several projects on different versions: tfenv. Don’t mix them casually. If you do, make sure tfenv comes first in your PATH.
Where does Terraform store downloaded providers?
In a .terraform folder inside each project, unless you’ve set a plugin cache. With plugin_cache_dir set, the cache holds the real files and projects reuse them.
Do I need the VS Code extension?
No, but it’s worth it. You get completion, hover docs, go to definition and format on save, all of which catch typos before terraform plan does.
Where to go next
New to the tool? Start with What is Terraform and how it works, which runs the init, plan, apply and destroy workflow with a demo that needs no cloud account. For command-by-command detail, see The Ultimate Guide to Installing and Using Terraform. And if you’ll keep your projects in git, installing and using Git and GitHub on Linux is the next stop.