Init #
terraform init is the first step you must always run before you can do anything with Terraform. Many developers treat it as a “run once” command they immediately forget about — yet understanding what happens behind the scenes of init is very helpful when troubleshooting unexpected provider, backend, or dependency issues. This section breaks down every initialization step, when you need to re-run it, which flags are important to know, and how to handle common problems.
What Happens During terraform init
#
terraform init isn’t just “download providers” — it performs several operations in sequence, each with its own failure modes.
flowchart TD
A["terraform init"] --> B["1. Read configuration\n.tf, variables.tf, etc."]
B --> C["2. Initialize the backend\nSet up remote state connection"]
C --> D["3. Download provider plugins\nPer required_providers"]
D --> E["4. Install modules\nExternal module references"]
E --> F["5. Update .terraform.lock.hcl\nRecord provider versions"]
F --> G["✅ Terraform has been\nsuccessfully initialized!"]
C -.->|"Failed?"| C1["Check backend config\nand credentials"]
D -.->|"Failed?"| D1["Check internet connection\nor provider constraints"]
E -.->|"Failed?"| E1["Check module source\nand version"]
style A fill:#e3f2fd,stroke:#1565c0
style G fill:#e8f5e9,stroke:#2e7d32
style C1 fill:#ffebee,stroke:#c62828
style D1 fill:#ffebee,stroke:#c62828
style E1 fill:#ffebee,stroke:#c62828| Step | What It Does | Output | Can Fail Because |
|---|---|---|---|
| 1. Read configuration | Parse all .tf files | — | HCL syntax error |
| 2. Initialize the backend | Connect to S3/GCS/Terraform Cloud | Backend ready | Wrong credentials, missing bucket |
| 3. Download providers | Pull binaries from the Registry | .terraform/providers/ | No internet, unsatisfied version constraint |
| 4. Install modules | Download external modules | .terraform/modules/ | Module source not found |
| 5. Update the lock file | Hash provider binaries | .terraform.lock.hcl | — |
Each of these steps can fail independently — understanding the order helps you know where to look for the problem.
Understanding Init Output #
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Finding cloudflare/cloudflare versions matching "~> 4.0"...
- Installing hashicorp/aws v5.31.0...
- Installed hashicorp/aws v5.31.0 (signed by HashiCorp)
- Installing cloudflare/cloudflare v4.20.0...
- Installed cloudflare/cloudflare v4.20.0 (signed by a HashiCorp partner, key ID ...)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections 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!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
Notice the “signed by HashiCorp” line — this is verification that the downloaded provider is the official one, not a counterfeit. If you see an unsigned provider or a suspicious signed by, investigate before continuing.
When to Re-run terraform init
#
Init isn’t only run once at the start. There are several conditions that require running it again.
flowchart TD
A["Project changes"] --> B{"What changed?"}
B -->|"New provider / version constraint"| C["Re-run init ✓"]
B -->|"Backend configuration"| D["Re-run init ✓"]
B -->|"New external module"| E["Re-run init ✓"]
B -->|"Clone on a new machine"| F["Re-run init ✓"]
B -->|"Resource config (main.tf)"| G["No init needed ✗"]
B -->|"Variable values (.tfvars)"| H["No init needed ✗"]
B -->|"Output definitions"| I["No init needed ✗"]
style C fill:#e3f2fd,stroke:#1565c0
style D fill:#e3f2fd,stroke:#1565c0
style E fill:#e3f2fd,stroke:#1565c0
style F fill:#e3f2fd,stroke:#1565c0
style G fill:#e8f5e9,stroke:#2e7d32
style H fill:#e8f5e9,stroke:#2e7d32
style I fill:#e8f5e9,stroke:#2e7d32| Change | Need Re-init? | Reason |
|---|---|---|
| Adding a new provider | ✅ Yes | Provider binary not downloaded yet |
| Changing a provider version constraint | ✅ Yes | May need a different version |
| Changing the backend configuration | ✅ Yes | The backend needs to be reconfigured |
| Adding a new external module | ✅ Yes | The module isn’t downloaded yet |
| Cloning on a new machine | ✅ Yes | .terraform/ doesn’t exist yet |
| Changing resources in main.tf | ❌ No | plan or apply is enough |
| Changing variable values | ❌ No | plan or apply is enough |
| Changing output definitions | ❌ No | plan or apply is enough |
Important Flags #
Each init flag has a specific use case. Using the wrong flag can cause state loss or an out-of-sync backend configuration.
flowchart TD
A["Need init with a flag?"] --> B{"What's the goal?"}
B -->|"Update providers to the\nlatest version in the constraint"| C["terraform init -upgrade"]
B -->|"Switch backends without\nmigrating state"| D["terraform init -reconfigure"]
B -->|"Move state to\na new backend"| E["terraform init -migrate-state"]
B -->|"Only install providers,\nno backend setup"| F["terraform init -backend=false"]
B -->|"Environment without\ninternet"| G["terraform init -plugin-dir=PATH"]
style C fill:#e3f2fd,stroke:#1565c0
style D fill:#fff3e0,stroke:#e65100
style E fill:#ffebee,stroke:#c62828
style F fill:#e8f5e9,stroke:#2e7d32
style G fill:#f3e5f5,stroke:#7b1fa2-upgrade
#
# Update providers to the latest version satisfying the constraint
terraform init -upgrade
# Without -upgrade: Terraform uses the versions recorded in .terraform.lock.hcl
# With -upgrade: Terraform ignores the lock file and looks for the latest version
# When to use:
# - After changing a version constraint (e.g. ~> 5.0 → ~> 5.1)
# - When you want the latest patches
# - When troubleshooting a provider bug that might already be fixed
-reconfigure
#
# Force a backend reconfigure without moving state
terraform init -reconfigure
# When to use:
# - The backend config in .tf changed but the state doesn't need migrating
# - A "Backend configuration changed" error that a plain init can't resolve
# - Moving from one S3 bucket to another (state was already copied manually)
-migrate-state
#
# Migrate state to a new backend interactively
terraform init -migrate-state
# When to use:
# - Moving from local state to a remote backend
# - Moving from S3 to Terraform Cloud
# - Moving from one remote backend to another
# ⚠️ BE CAREFUL: Make sure a state backup exists before migrating
# If the migration fails midway, the state can be lost
-backend=false
#
# Init without backend setup
terraform init -backend=false
# When to use:
# - CI/CD pipelines that only need plan (no state needed)
# - Testing configuration without connecting to the remote backend
# - terraform validate in PR checks
-plugin-dir
#
# Use provider binaries from a local directory
terraform init -plugin-dir=/path/to/local-mirror
# When to use:
# - Environments without internet access (air-gapped)
# - CI/CD that caches provider binaries on shared storage
# - Security: downloading from the internet isn't allowed
| Flag | Function | Risk | Frequency |
|---|---|---|---|
-upgrade | Update providers | Low — can be reverted | Occasionally |
-reconfigure | Reconfigure the backend | Medium — make sure state is safe | Rarely |
-migrate-state | Migrate state | High — back it up first! | Very rarely |
-backend=false | Skip the backend | Low — no state | CI/CD |
-plugin-dir | Local provider mirror | Low | Air-gapped envs |
The .terraform Directory Structure After Init
#
Understanding the contents of .terraform/ helps during troubleshooting — you know which files should exist and what happens if one goes missing.
.terraform/
├── providers/
│ └── registry.terraform.io/
│ ├── hashicorp/
│ │ └── aws/
│ │ └── 5.31.0/
│ │ └── linux_amd64/
│ │ └── terraform-provider-aws_v5.31.0_x5
│ └── cloudflare/
│ └── cloudflare/
│ └── 4.20.0/
│ └── linux_amd64/
│ └── terraform-provider-cloudflare_v4.20.0
└── terraform.tfstate
(backend metadata, not infrastructure state)
This directory doesn’t need to be committed to Git. Only .terraform.lock.hcl gets committed, and every team member gets identical provider binaries when they run terraform init.
# What to commit vs not:
# .terraform/ ← DON'T commit (binaries, can be regenerated)
# .terraform.lock.hcl ← MUST commit (locks provider versions)
Init in an Environment Without Internet #
In corporate or air-gapped environments, Terraform can’t access the Terraform Registry. You need to prepare a provider mirror in advance.
flowchart LR
subgraph "Machine with Internet"
A["terraform providers mirror\n/path/to/mirror"] --> B["Provider binaries\ndownloaded locally"]
end
subgraph "Transfer"
B --> C["Copy the mirror\nto the air-gapped machine"]
end
subgraph "Machine without Internet"
C --> D["terraform init\n-plugin-dir=/path/to/mirror"]
D --> E["Init succeeds\nwithout internet!"]
end
style A fill:#e3f2fd,stroke:#1565c0
style E fill:#e8f5e9,stroke:#2e7d32# On a machine with internet access:
# Download all required providers
terraform providers mirror /path/to/local-mirror
# Copy the result to the offline machine (USB, internal network, etc.)
# Then run:
terraform init -plugin-dir=/path/to/local-mirror
Alternative: configure a filesystem mirror in ~/.terraformrc so it’s always used.
# ~/.terraformrc — provider installation configuration
provider_installation {
filesystem_mirror {
path = "/usr/share/terraform/providers"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
Common Problems and Solutions #
| Error | Cause | Solution |
|---|---|---|
Failed to query available provider packages | No connection to the registry | Check internet, or use -plugin-dir |
Required plugins are not installed | .terraform/ doesn’t exist / was deleted | Run terraform init |
Backend configuration changed | Backend config differs from what’s stored | init -reconfigure or init -migrate-state |
Lock file not compatible | Lock file generated on a different platform | terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 |
Failed to read backend config | Wrong backend credentials | Check credentials and permissions |
Module not found | Module source is wrong / inaccessible | Check the source path and version |
# Multi-platform lock file (if the team uses different OSes):
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=windows_amd64
# Run this from a machine with access to all providers
# So the lock file includes hashes for every platform
Summary #
terraform initdoes 4 things: initializes the backend, downloads providers, installs modules, and updates the lock file — each step can fail independently.- Re-run it whenever there are changes to providers, backends, or external modules — not just at project start.
- Commit
.terraform.lock.hcl, don’t commit the.terraform/directory — this ensures all team members use identical provider versions.-upgradeto update providers,-reconfigureto switch backends without state migration,-migrate-stateto switch backends with migration — pick the right flag for your situation.- Back up state before
-migrate-state— a migration that fails midway can lose state.- Internet-free environments need a local provider mirror — prepare
terraform providers mirrorbefore deploying to restricted environments.- Multi-platform lock files — if the team uses different OSes, run
terraform providers lockwith multiple-platformflags.