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
StepWhat It DoesOutputCan Fail Because
1. Read configurationParse all .tf filesHCL syntax error
2. Initialize the backendConnect to S3/GCS/Terraform CloudBackend readyWrong credentials, missing bucket
3. Download providersPull binaries from the Registry.terraform/providers/No internet, unsatisfied version constraint
4. Install modulesDownload external modules.terraform/modules/Module source not found
5. Update the lock fileHash 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
ChangeNeed Re-init?Reason
Adding a new provider✅ YesProvider binary not downloaded yet
Changing a provider version constraint✅ YesMay need a different version
Changing the backend configuration✅ YesThe backend needs to be reconfigured
Adding a new external module✅ YesThe module isn’t downloaded yet
Cloning on a new machine✅ Yes.terraform/ doesn’t exist yet
Changing resources in main.tf❌ Noplan or apply is enough
Changing variable values❌ Noplan or apply is enough
Changing output definitions❌ Noplan 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
FlagFunctionRiskFrequency
-upgradeUpdate providersLow — can be revertedOccasionally
-reconfigureReconfigure the backendMedium — make sure state is safeRarely
-migrate-stateMigrate stateHigh — back it up first!Very rarely
-backend=falseSkip the backendLow — no stateCI/CD
-plugin-dirLocal provider mirrorLowAir-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 #

ErrorCauseSolution
Failed to query available provider packagesNo connection to the registryCheck internet, or use -plugin-dir
Required plugins are not installed.terraform/ doesn’t exist / was deletedRun terraform init
Backend configuration changedBackend config differs from what’s storedinit -reconfigure or init -migrate-state
Lock file not compatibleLock file generated on a different platformterraform providers lock -platform=linux_amd64 -platform=darwin_arm64
Failed to read backend configWrong backend credentialsCheck credentials and permissions
Module not foundModule source is wrong / inaccessibleCheck 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 init does 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.
  • -upgrade to update providers, -reconfigure to switch backends without state migration, -migrate-state to 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 mirror before deploying to restricted environments.
  • Multi-platform lock files — if the team uses different OSes, run terraform providers lock with multiple -platform flags.

← Previous: Directory Structure   Next: Plan →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact