Terraform init Command with Examples

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.15.8-1
hashicorp/local provider 2.9.0
Applies to Any host with Terraform installed
Lab environment Single Ubuntu VM with Terraform — Terraform lab environment on Ubuntu
Privilege Normal user
Scope terraform init workflow — backend setup, provider and module installation, .terraform and .terraform.lock.hcl, reinitialization, clone workflow, and common flags (-upgrade, -reconfigure, -migrate-state, -backend=false, -backend-config). Does not cover Terraform installation, provider version strategy, module authoring, remote backend setup, or plan/apply tutorials.
Related guides Terraform lab environment on Ubuntu
Terraform providers
Provider version constraints and lock file
Terraform HCL syntax
Terraform Associate certification course

Every Terraform workflow on a machine starts with one command:

bash
terraform init

terraform init initializes a Terraform working directory. It prepares the backend, downloads provider plugins and modules your configuration references, processes dependency lock selections, and writes the local metadata Terraform needs before plan or apply can run. This guide walks through that process on Ubuntu with the hashicorp/local provider — no cloud credentials required.

NOTE
Use the Terraform lab environment on Ubuntu and confirm terraform is installed before you start. Installation steps belong in install Terraform on Ubuntu, not here.

What does terraform init do?

terraform init reads the .tf files in the current directory and performs several setup steps:

  • Backend initialization — configures where Terraform stores state (default local backend unless you declare another)
  • Provider installation — downloads provider plugins declared in required_providers
  • Module installation — discovers local modules and downloads remote module packages
  • Dependency lock processing — creates or updates .terraform.lock.hcl with selected provider versions and checksums
  • Working-directory preparation — writes plugin and module metadata under .terraform/
text
*.tf configuration
terraform init
      ├── backend
      ├── providers
      ├── modules
      └── dependency selections

Init is safe to rerun. Later runs are usually quick when nothing changed; Terraform refreshes only what the configuration now requires.


Run terraform init

This section walks the full init cycle: create configuration, run init, inspect what appeared, rerun init, and verify the directory is ready.

Lab configuration

Use an isolated directory under your lab tree:

bash
mkdir -p ~/terraform-labs/terraform-init

Move into it — every command below assumes you are here:

bash
cd ~/terraform-labs/terraform-init

Add a minimal resource in main.tf:

hcl
resource "local_file" "demo" {
  content  = "init lab"
  filename = "${path.module}/demo.txt"
}

Declare the provider requirement in versions.tf:

hcl
terraform {
  required_version = ">= 1.12.0"

  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

The source address tells Terraform which registry plugin to download. Constraint and lock-file depth live in provider version constraints and lock file; this page focuses on what init does with those settings.

First init

Initialize the working directory:

bash
terraform init

Sample output:

output
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

Read the phases in order:

  1. Initializing the backend — Terraform sets up the configured backend (implicit local backend here).
  2. Initializing provider plugins — Terraform resolves the constraint, downloads hashicorp/local v2.9.0, and installs it under .terraform/providers/.
  3. Lock file message — Terraform records the selected provider version in .terraform.lock.hcl.
  4. Successfully initialized — the directory is ready for validation and planning.

Files and directories created

List the working directory after the first init:

bash
ls -la

Sample output:

output
drwxr-xr-x  3 user user 4096 Aug 11 21:08 .terraform
-rw-r--r--  1 user user 1257 Aug 11 21:08 .terraform.lock.hcl
-rw-r--r--  1 user user   96 Aug 11 21:07 main.tf
-rw-r--r--  1 user user  153 Aug 11 21:07 versions.tf

Inspect the .terraform tree:

bash
find .terraform -maxdepth 3 -type d

Sample output:

output
.terraform
.terraform/providers
.terraform/providers/registry.terraform.io
.terraform/providers/registry.terraform.io/hashicorp
Path Created by init Purpose
.terraform/ Yes Local metadata — downloaded providers, module records, backend settings
.terraform.lock.hcl Yes Provider version selections and package checksums
terraform.tfstate No (this lab) State appears after terraform apply changes infrastructure

Second init

Run init again without changing any files:

bash
terraform init

Sample output:

output
Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.9.0

Terraform has been successfully initialized!

Terraform skips redundant downloads when the lock file and installed plugins already match the configuration.

Verification

Confirm which providers init installed:

bash
terraform providers

Sample output:

output
Providers required by configuration:
.
└── provider[registry.terraform.io/hashicorp/local] ~> 2.5

Check that the configuration is internally consistent:

bash
terraform validate

Sample output:

output
Success! The configuration is valid.

Together, terraform providers and terraform validate confirm init prepared dependencies and the .tf files parse correctly. You do not need terraform apply on this page to prove initialization succeeded.

text
create configuration
terraform init
inspect .terraform / lock file
rerun init
terraform providers
terraform validate

When should you run terraform init again?

Situation Why init is needed
New working directory No .terraform/ metadata exists yet on this machine
Cloned repository .terraform/ is not in version control; plugins must be downloaded locally
Provider requirement changes New or updated required_providers entries need plugin resolution
Module source or version changes Terraform must fetch or refresh module content
Backend configuration changes Backend metadata must be reconfigured or migrated
Deliberate provider upgrade Pass -upgrade to refresh selections within constraints

Teams commit .tf files and .terraform.lock.hcl, not .terraform/. After you clone a repository, initialize before any other Terraform command:

bash
git clone https://github.com/example/infrastructure.git

Enter the cloned repository:

bash
cd infrastructure

Download providers and prepare the working directory on this machine:

bash
terraform init

Each machine downloads its own provider plugins into .terraform/. The lock file keeps provider version selections consistent across laptops and CI runners.

If terraform plan reports that backend initialization is required, init was skipped or the backend block changed since the last successful init.


terraform init options

Common flags that change how init behaves:

Flag Purpose
-upgrade Reconsider provider versions within configured constraints and refresh lock-file selections
-reconfigure Reinitialize the backend; ignore prior backend metadata without migrating state
-migrate-state Copy or move existing state when backend configuration changed
-backend=false Skip backend configuration during this init
-backend-config=FILE Merge extra backend settings from a file or key=value pairs
-get=false Skip downloading or updating child modules during init

The subsections below demonstrate the flags you are most likely to use in daily work.

-upgrade

Pass -upgrade when you want Terraform to reconsider provider versions within your configured constraints:

bash
terraform init -upgrade

Sample output when the locked version is already the newest match:

output
Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Using previously-installed hashicorp/local v2.9.0

Terraform has been successfully initialized!

Use -upgrade deliberately after you widen a constraint or when you intend to adopt a newer provider release. Review .terraform.lock.hcl diffs and run terraform plan before apply. Constraint operators and lock-file policy live in provider version constraints and lock file.

Reconfigure or migrate a changed backend

Use a separate lab directory so backend experiments do not overwrite the main init exercise:

bash
mkdir -p ~/terraform-labs/terraform-init-backend

Switch into that backend lab directory:

bash
cd ~/terraform-labs/terraform-init-backend

Add main.tf with the same local_file resource as earlier. Start with a local backend that writes state to one file — add this to versions.tf:

hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }

  backend "local" {
    path = "state-old.tfstate"
  }
}

Initialize and create state with apply:

bash
terraform init

terraform init exits successfully when the backend and providers are configured. Create managed resources so state exists in state-old.tfstate:

bash
terraform apply -auto-approve

Change the backend path in versions.tf:

hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }

  backend "local" {
    path = "state-new.tfstate"
  }
}

HashiCorp treats -reconfigure and -migrate-state as alternative responses to a backend configuration change — not steps you run one after the other.

  • If you run -reconfigure first, Terraform already accepts the new backend without migrating state
  • A follow-up -migrate-state no longer demonstrates migration from the original backend
Flag Behavior
terraform init -reconfigure Accept the new backend settings and ignore prior backend metadata. Existing state is not copied — state-old.tfstate keeps the old records while Terraform points at state-new.tfstate.
terraform init -migrate-state Detect the prior backend, then offer to copy existing state into the new backend.

This lab demonstrates -migrate-state end to end. Use a fresh copy of the lab directory if you also want to try -reconfigure separately.

Run migrate init and answer the prompt when Terraform asks whether to copy state:

bash
terraform init -migrate-state

Sample prompt:

output
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.

Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "local" backend. No existing state was found in the newly
  configured "local" backend. Do you want to copy this state to the new "local"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value:

Answer yes to copy state into state-new.tfstate. After migration, state-new.tfstate holds the managed resource records Terraform created during apply.

Choose -reconfigure instead when you deliberately want the new backend settings without copying state — for example when you are pointing at a fresh backend and do not need the old state file. Full remote backend setup (S3, HCP Terraform, and production patterns) is out of scope here.

-backend=false

-backend=false skips backend configuration during this init. It is mainly useful in a working directory that was already initialized for its backend. It is also useful when preparing an initialized dependency tree specifically for terraform validate without accessing the configured backend:

bash
terraform init -backend=false

Sample output:

output
Initializing provider plugins...
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.9.0

Terraform has been successfully initialized!

The output omits Initializing the backend. HashiCorp documents this pattern for validation-only workflows:

bash
terraform validate

terraform init -backend=false prepares providers and modules without initializing the backend, then terraform validate checks the configuration — handy when you want to check configuration syntax before the backend is reachable.

-backend-config

Some teams split backend settings across files. Declare the backend type in configuration and pass remaining settings at init time.

versions.tf with a partial local backend:

hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }

  backend "local" {}
}

local.tfbackend supplies the state file path — HashiCorp documents the *.backendname.tfbackend naming pattern:

hcl
path = "state/terraform.tfstate"

Initialize with the extra backend settings:

bash
terraform init -backend-config=local.tfbackend

Sample output:

output
Initializing the backend...

Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You can also pass -backend-config=key=value multiple times. Remote backend design belongs in a dedicated state/backends lesson — here you only need to recognize the flag.


Initialize Terraform modules

terraform init also processes modules. Add a tiny local module and reference it from the root module.

Create modules/greet/main.tf:

hcl
resource "local_file" "greeting" {
  content  = var.message
  filename = "${path.module}/greeting.txt"
}

Create modules/greet/variables.tf:

hcl
variable "message" {
  type = string
}

Reference the module in main.tf:

hcl
module "greet" {
  source  = "./modules/greet"
  message = "hello from module"
}

Run init in the root module directory:

bash
terraform init

Sample output:

output
Initializing the backend...

Initializing modules...
- greet in modules/greet

Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)

Terraform has been successfully initialized!

The Initializing modules phase records module metadata in .terraform/modules/modules.json.

  • Remote module packages are downloaded under .terraform/modules/
  • For a relative local source such as ./modules/greet, Terraform refers to the existing module directory rather than maintaining a separate downloaded copy
  • Pass -get=false when you want terraform init to skip child-module installation while still performing the other initialization steps

terraform init vs plan and validate

Command Main purpose Needs init first Downloads dependencies Changes infrastructure
terraform init Prepare working directory No Yes No
terraform validate Validate configuration Normally initialized first No No
terraform plan Preview changes Yes No No

Run terraform init once per machine per working directory (and again when dependencies change). Run terraform validate for a fast syntax and consistency check. Run terraform plan when you want to see what would change against current state.


Common terraform init errors

Symptom Likely cause Fix
Empty directory message No .tf files present Add configuration files, or cd into the correct module root
Failed to query available provider packages Bad version constraint or registry unreachable Fix required_providers.version — see provider version constraints and lock file
Provider registry does not have a provider named … Wrong source address in required_providers Correct the provider source — see Terraform providers
Backend initialization required plan or apply before init with a custom backend Run terraform init in the module root
Unreadable module directory module source path does not exist Fix the module path
Provider package checksum mismatch Downloaded package does not match lock-file hashes Refresh locks or fix mirrors — see provider version constraints and lock file

Unavailable provider version

Set version = "= 99.0.0" in required_providers, then init:

bash
terraform init

Sample output:

output
Error: Failed to query available provider packages

Could not retrieve the list of available versions for provider
hashicorp/local: no available releases match the given constraints 99.0.0

Pick a published version or widen the constraint.

Incorrect provider source

Use a mistyped registry address in required_providers, then init:

bash
terraform init

Sample output:

output
Error: Failed to query available provider packages

Could not retrieve the list of available versions for provider
hashicorp/nonexistent-provider-xyz: provider registry registry.terraform.io
does not have a provider named
registry.terraform.io/hashicorp/nonexistent-provider-xyz

Verify the source value in required_providers.

Module source or download failure

Point a module block at a path that does not exist, then init:

bash
terraform init

Sample output:

output
Error: Unreadable module directory

Unable to evaluate directory symlink: lstat does-not-exist: no such file or
directory

Fix the source path in the module block.


References


Summary

terraform init turns a folder of .tf files into a ready Terraform working directory on your machine:

  • Configures the backend
  • Downloads provider plugins
  • Processes modules
  • Writes .terraform.lock.hcl
  • Stores metadata under .terraform/

A second init on an unchanged configuration reuses installed plugins and lock selections instead of downloading everything again.

You practiced the natural workflow on Ubuntu: create configuration, run init, inspect .terraform/ and the lock file, rerun init, then confirm readiness with terraform providers and terraform validate.

You also saw when to init again — new directories, clones, dependency changes, and backend updates — and how flags such as -upgrade, -reconfigure, -migrate-state, -backend=false, and -backend-config adjust init behavior without replacing the core command.

The mistake to avoid is skipping init after a clone or dependency change. When init fails, match the error to provider source, version constraints, backend setup, or module paths; use the providers and lock-file guides for deeper troubleshooting.

Next in this course track: terraform validate, then terraform fmt, then terraform plan.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)