Initializing Enclave...

How to Fix Terraform 'Error: no value for required variable' (TF_VAR Missing)

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 5 mins

TL;DR

  • What broke: Terraform halted at plan or apply because a variable block has no default and no value was injected via TF_VAR_*, -var, or a .tfvars file — Terraform refuses to proceed, full stop.
  • How to fix it: Supply the missing value via an exported TF_VAR_<name> environment variable, a -var CLI flag, a terraform.tfvars file, or add a default to the variable declaration if appropriate.
  • Fastest path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing variables.tf and it will generate the corrected variable blocks and a matching terraform.tfvars stub locally without sending your config anywhere.

The Incident (What does the error mean?)

Raw error output from terraform plan:

╷
│ Error: No value for required variable
│
│   on variables.tf line 1:
│   1: variable "db_password" {
│
│ The root module input variable "db_password" is not set, and has no default
│ value. Use a -var or -var-file command line argument to provide a value for
│ this variable.
╵

Immediate consequence: Terraform exits with code 1. No plan is generated. No diff is shown. The entire pipeline — whether local, CI, or CD — is dead in the water until every required variable is satisfied. In a CD pipeline this means a deployment gate is fully blocked, and any downstream terraform apply step never executes.

Terraform's variable resolution order is strict and deterministic:

  1. TF_VAR_<name> environment variables
  2. terraform.tfvars or terraform.tfvars.json in the working directory
  3. *.auto.tfvars or *.auto.tfvars.json files
  4. -var and -var-file CLI flags (in order passed)
  5. default value in the variable block

If none of these yield a value, Terraform throws this error before any provider API call is made.


The Attack Vector / Blast Radius

This is not just a nuisance — the failure mode has real blast radius in automated pipelines:

CI/CD pipeline collapse: A terraform plan step that exits non-zero in GitHub Actions, GitLab CI, or Atlantis will cascade: the plan artifact is never written, the policy-as-code gate (Sentinel, OPA) never fires, and the apply job is either skipped or — worse — if your pipeline is misconfigured with continue-on-error: true, it proceeds with a stale plan from a previous run. You apply yesterday's infrastructure state against today's code.

Secret sprawl from workarounds: Engineers under pressure hardcode values directly into variables.tf as default values to unblock themselves. Secrets (db_password, api_key, private_key_pem) end up committed to version control. This is the most common downstream security incident that originates from this error.

Environment drift: When TF_VAR_* variables are set inconsistently across dev, staging, and prod pipeline runners, the same Terraform module produces different infrastructure. Variable values that exist in one environment's CI secrets store but not another cause intermittent plan failures that are hard to reproduce locally.

Workspace/module confusion: In multi-workspace or root-module-calls-child-module setups, the error message only reports the root module variable. If the missing variable is actually being passed down to a child module, the trace can be misleading and engineers waste time looking at the wrong file.


How to Fix It (The Solution)

Basic Fix — Option A: Export TF_VAR in Shell

The fastest fix for local development. Never do this with secrets in shell history — use -var-file or a secrets manager instead.

- # Variable has no value, plan fails
- terraform plan

+ export TF_VAR_db_password="$(vault kv get -field=password secret/prod/db)"
+ terraform plan

Basic Fix — Option B: terraform.tfvars File

Create a terraform.tfvars file in the working directory. Add this file to .gitignore immediately if it contains secrets.

- # No terraform.tfvars exists, or it is missing the variable

+ # terraform.tfvars
+ db_password = "changeme-local-only"
+ region      = "us-east-1"
+ environment = "dev"

Basic Fix — Option C: Add a Default (Non-Secret Variables Only)

Only appropriate for non-sensitive variables like region, environment name, or instance type.

  variable "region" {
    description = "AWS region to deploy into"
    type        = string
-   # No default — forces caller to always supply it
+   default     = "us-east-1"
  }

  variable "db_password" {
    description = "RDS master password"
    type        = string
+   sensitive   = true
-   # DO NOT add a default for secrets
  }

Enterprise Best Practice — Secrets Manager + Ephemeral Env Injection

In production pipelines, never pass secrets as plaintext TF_VAR values. Use dynamic secret injection at plan/apply time:

- # GitLab CI / GitHub Actions — hardcoded secret in CI variable
- env:
-   TF_VAR_db_password: "hardcoded-secret-123"

+ # GitHub Actions — secret fetched from AWS Secrets Manager at runtime
+ - name: Fetch DB password from Secrets Manager
+   run: |
+     export TF_VAR_db_password=$(aws secretsmanager get-secret-value \
+       --secret-id prod/rds/master-password \
+       --query SecretString \
+       --output text | jq -r .password)
+     echo "TF_VAR_db_password=$TF_VAR_db_password" >> $GITHUB_ENV

For HashiCorp Vault:

- # Static secret baked into pipeline environment
- TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}

+ # Vault dynamic secret — leased, auto-rotated, audited
+ - name: Authenticate to Vault
+   uses: hashicorp/vault-action@v2
+   with:
+     url: https://vault.corp.example.com
+     method: jwt
+     secrets: |
+       secret/data/prod/rds password | TF_VAR_db_password ;

For variable validation to catch bad values early (not just missing values):

  variable "environment" {
    description = "Deployment environment"
    type        = string
+   validation {
+     condition     = contains(["dev", "staging", "prod"], var.environment)
+     error_message = "environment must be one of: dev, staging, prod."
+   }
  }

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Checkov — static scan for missing variable defaults on sensitive vars:

checkov -d . --check CKV_TF_1 --framework terraform

Add to your pipeline before terraform plan:

# .github/workflows/terraform.yml
- name: Checkov Scan
  uses: bridgecrewio/checkov-action@master
  with:
    directory: ./infra
    framework: terraform
    soft_fail: false

2. tflint — catches undeclared variables and missing required inputs:

tflint --init
tflint --var-file=terraform.tfvars

.tflint.hcl config:

plugin "terraform" {
  enabled = true
  preset  = "recommended"
}

rule "terraform_required_providers" {
  enabled = true
}

3. OPA/Conftest — policy gate that rejects plans with sensitive variables lacking sensitive = true:

# policy/terraform_vars.rego
package terraform.variables

deny[msg] {
  var := input.variables[name]
  regex.match("(password|secret|key|token|credential)", lower(name))
  not var.sensitive
  msg := sprintf("Variable '%v' appears to be sensitive but 'sensitive = true' is not set.", [name])
}
terraform show -json tfplan.binary | conftest test --policy policy/ -

4. Required variable audit script — run before every terraform init:

#!/usr/bin/env bash
# check_tf_vars.sh — exits non-zero if any required TF_VAR is unset
REQUIRED_VARS=("db_password" "api_key" "environment" "region")
MISSING=()

for var in "${REQUIRED_VARS[@]}"; do
  if [[ -z "${!var+x}" ]] && [[ -z "$(printenv TF_VAR_${var})" ]]; then
    MISSING+=("TF_VAR_${var}")
  fi
done

if [[ ${#MISSING[@]} -gt 0 ]]; then
  echo "ERROR: Missing required Terraform variables:"
  printf '  - %s\n' "${MISSING[@]}"
  exit 1
fi

echo "All required TF_VAR_* variables are set."

5. Pre-commit hook — block commits that introduce required variables without defaults:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.83.5
    hooks:
      - id: terraform_tflint
      - id: terraform_validate
      - id: checkov
        args:
          - --args=--check CKV_TF_1

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →