Skip to main content
RunBook Academy

TerraformXVIII · Troubleshooting and RecoveryProduction Terraform

Troubleshooting Configuration Errors

Intermediate⏱ ~12 minbash

What you'll learn

  • Diagnose HCL syntax and semantic errors without mutating production state
  • Trace a wrong provider from source address through install, configuration, and plan output
  • Verify module source resolution and distinguish local, registry, and versioned sources
  • Use validation and focused plan commands to test one configuration hypothesis
  • Recover configuration safely while preserving provider and module version controls

Prerequisites

None — start here.

Verified against Terraform CLI 1.9.x · OpenTofu 1.7.x · HCL 2.0 · bpg/proxmox provider 0.66+ · hashicorp/local provider 2.5+ · hashicorp/null provider 3.2+ · hashicorp/random provider 3.6+ · hashicorp/http provider 3.4+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-13

Not yet marked complete on this device.

Terraform configuration errors are failures in the declarative description of infrastructure: HCL syntax, provider selection and version, module source resolution, variable values, or the graph built from references. They are usually found before a provider API call, but an error can be reported later when an invalid value reaches a provider. The goal is to find the earliest failing boundary.

The configuration is the source of intent. State is evidence of what Terraform previously recorded. Do not repair a bad plan by changing state when the problem is in .tf files or module/provider metadata.

The configuration diagnostic order

Use a clean, exact working copy of the revision under investigation. Record the Terraform version, working directory, backend, workspace, and any environment-provided variables. Do not begin with an apply.

  1. Reproduce the error. Run formatting checks and validation with the same revision and provider/plugin cache conditions.
  2. Classify the boundary. Parser, provider installation, provider configuration, module installation, graph construction, or provider API.
  3. Inspect the selected source. Check the exact provider source address and module source, including relative paths and version constraints.
  4. Test one hypothesis. Change only the suspected configuration or dependency declaration, then run a read-only or saved plan.
  5. Apply the smallest fix. Preserve version locks, review the plan, and document any deliberate upgrade or module migration.
  6. Verify the result. Run validation, a plan, and the service-level checks relevant to the affected resources.

HCL syntax: fix structure before semantics

A missing brace, quote, comma, or attribute separator is a parser failure. Formatting can make the file easier to inspect, but it cannot fix an unsupported argument or an invalid value.

This fragment is intentionally incomplete:

resource "aws_instance" "web" {
  ami           = var.web_ami
  instance_type = "t3.small"

A parser reports a message such as:

Error: Missing attribute separator
  on main.tf line 5, in resource "aws_instance" "web":
   5:   instance_type = "t3.small"

Expected an attribute name or a closing brace.

Add the missing close brace, then run the check again:

resource "aws_instance" "web" {
  ami           = var.web_ami
  instance_type = "t3.small"
}

READ-ONLY

terraform fmt -check -recursive -diff
terraform validate

fmt -check reports files that are not canonical without rewriting them. validate reports configuration and provider schema problems after the working directory has been initialised. If either command changes no file, you have preserved a useful baseline for comparison.

The wrong provider

A provider has two identities in the configuration: the local name used by resources and the canonical source address used to install the plugin. A common mistake is to write hashicorp/aws as if it were the local name, or to change a resource type without changing the provider source and version constraint.

terraform {
  required_version = ">= 1.9.0, < 2.0.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}

aws is the local provider name. hashicorp/aws is the registry source address. The version constraint controls which compatible provider releases Terraform may select. A lock file records the selected versions and checksums for the platforms used by the team.

The provider problem can occur at three different stages:

  1. Provider installation. terraform init cannot find, download, or verify the selected plugin. Check the required-provider block and the lock file.
  2. Provider configuration. The provider block lacks a required argument, uses the wrong region, or points at an invalid endpoint. terraform validate may report the first category of error.
  3. Provider runtime. Credentials, permissions, API quotas, or resource schemas fail only during planning or apply. Inspect the provider’s operation and environment, not just the HCL line.

CONFIGURATION — downloads or verifies provider and module metadata and may write .terraform and lock data. Do not add -upgrade to a production diagnosis without an explicit review.

terraform init -backend=false -input=false
terraform providers

Illustrative output from terraform providers:

provider[registry.terraform.io/hashicorp/aws] 5.80.0

The important field is the canonical address. If the output names a different provider, or if the version is outside the constraint, stop and correct the requirement or lock decision. A missing provider can also be caused by a local plugin mirror or an unavailable network, so collect that evidence before changing the source.

The wrong module source

A module’s source tells Terraform where to download code. It is a path, registry address, or supported package source, not a friendly label. Relative paths resolve from the directory containing the module call. A local module can be tested without a registry version, while a registry module can be constrained to a version or version range.

module "network" {
  source = "../modules/network"

  vpc_cidr = "10.20.0.0/16"
}

The path is correct only when ../modules/network exists from this module’s directory. A common failure is to copy a path from the root working directory into a child module, where the same relative path points at a different location. The fix is to state the intended working directory and test the source in a clean checkout.

CONFIGURATION — downloads module code into the local module cache. It does not apply the module.

terraform get
terraform init -backend=false -input=false
terraform validate
terraform plan -input=false -no-color -refresh-only

For a registry module, pin a reviewed version instead of relying on a moving tag:

module "network" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.5"
}

Do not use a version argument with a local path; Terraform does not treat a local directory as a versioned registry package. If a module source changes, compare the provider requirements and resource addresses, and expect a potentially different graph. A source change is a configuration change, not a harmless line edit.

Five configuration failure modes

1. Syntax error hidden by a vague wrapper

Observable symptom. terraform plan says “could not load plugin” or produces an internal error while the actual message contains a missing separator or unclosed expression.

Recovery. Run terraform fmt -check -recursive -diff and terraform validate first, then inspect the first parser diagnostic. Correct the HCL structure; do not alter provider code or state to work around it.

2. Provider name and source address disagree

Observable symptom. Resources use aws_instance, but the required_providers source is omitted, misspelled, or points to a different provider. The error may say the provider is not available or the resource type is unsupported.

Recovery. Read terraform providers, verify the canonical address and local name, pin a compatible version, and run init without an upgrade. A resource rename is a separate migration and needs a plan; do not perform it merely to silence an installation error.

3. Provider credentials are mistaken for a syntax problem

Observable symptom. init succeeds and validate succeeds, but planning returns No valid credential sources, AccessDenied, or a region-specific API error.

Recovery. Verify the provider’s region, account, credentials helper, and environment variables. Do not paste a token into HCL or a ticket; use the approved short-lived identity mechanism.

4. Module path resolves from the wrong directory

Observable symptom. terraform get cannot find the module, or it loads a different module with a valid but unintended name. The resulting resource addresses or inputs do not match the reviewed configuration.

Recovery. Confirm the current working directory, calculate the relative path from the module call, and test in a clean checkout. If the source is changing from local to registry, review the module’s provider and variable contract before planning.

5. A format or validate pass is treated as a safe apply

Observable symptom. CI shows both checks green, yet the plan proposes a replacement or the provider rejects an existing value. Formatting and validation are deterministic checks; they do not query the cloud or review blast radius.

Recovery. Require a saved plan and a second review for every non-empty plan. Use -detailed-exitcode in automation so a non-empty plan is not mistaken for a no-op result.

Security and performance

Providers and modules are executable supply-chain inputs. Pin provider versions and checksums in the lock file, review registry modules before adoption, and avoid a source that changes every run because it points at an unpinned moving tag. terraform init downloads and starts the provider installation workflow, so it belongs inside a controlled developer or CI environment with an approved plugin cache.

Repeated init and module downloads add network and CPU overhead. A shared, permissioned plugin cache can reduce downloads, but it must still enforce the selected versions. Run formatting and validation before network-heavy plan work, and keep modules small enough to review and test.

Production guidance

  • Pin Terraform, providers, and registry modules; commit and review the lock file.
  • Use a clean checkout and a consistent plugin cache for diagnosing failures in CI.
  • Separate provider installation, provider authentication, and provider runtime errors in the incident record.
  • Require a reviewed plan and a rollback revision before applying a module or provider change.
  • Test configuration recovery in staging, including a failed init, wrong source path, and unexpected provider default.

Verification

  • You can run formatting and validation checks without changing the files under investigation.
  • You can identify the local provider name and canonical provider source address separately.
  • You can diagnose a module path from the directory containing the module call.
  • You can distinguish a provider install error from an authentication or runtime error.
  • You can use a saved plan and detailed exit status to prove that a configuration is not ready to apply.
  • You can pin and review providers and modules before production recovery.

Knowledge check · 7 questions

  1. Q1. What is the best first diagnostic sequence for a configuration error?

  2. Q2. In a provider declaration, what is the distinction between `aws` and `hashicorp/aws`?

  3. Q3. Formatting a Terraform file changes layout and quoting only, so an unsupported provider argument or an invalid external value survives it untouched.

  4. Q4. Which checks help separate a configuration problem from a provider runtime problem? (Select all that apply.)

  5. Q5. `terraform get` reports that a local module cannot be found. The module call is one directory deeper than expected. What is the likely cause?

  6. Q6. Why should a production configuration recovery prefer a reviewed `.tf` change over a one-off local state edit?

  7. Q7. What does `terraform providers` help an operator inspect?

Passing score: 75%. Answers are checked in this browser.