terraform validate 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 validate — scope, successful validation, syntax and schema errors, invalid references, type mismatches, -json output, init -backend=false workflow, comparisons with plan and fmt, and a brief CI pattern. Does not cover custom validation blocks, Checkov or TFLint, cloud authentication, state locking, or full pipeline design.
Related guides terraform init
Terraform HCL syntax
Terraform providers
Terraform lab environment on Ubuntu
Terraform Associate certification course

After you edit .tf files, the fastest sanity check is:

bash
terraform validate

terraform validate tells you whether Terraform can parse your configuration and whether arguments, types, and references line up with installed provider schemas. It runs locally and finishes in seconds, which makes it ideal before you commit or open a pull request.

Every command in this walkthrough runs in one working directory, ~/terraform-labs/terraform-validate/. You initialize the lab once, then overwrite main.tf with heredocs for each example so you can copy the article step by step.

NOTE
Complete install Terraform on Ubuntu and the Terraform lab environment first. Provider installation belongs in terraform init, not here.

What terraform validate checks

terraform validate is a static check against your configuration and the schemas Terraform loaded from installed providers and modules.

It covers:

  • HCL syntax — braces, quotes, and block structure must parse
  • Internal consistency — resource types, argument names, and references must make sense in the module where they appear
  • Schema rules from installed providers — required arguments, supported attributes, and expression types the provider schema exposes

It does not prove that your infrastructure will work in the real world. Validation cannot confirm:

  • Cloud or API credentials are valid
  • Remote resources you reference actually exist
  • A provider can complete create, update, or destroy at runtime
  • Remote service settings outside Terraform’s schema are correct

Treat Success! The configuration is valid. as “the configuration is well formed,” not “production is safe to apply.”

text
validate  →  configuration + schemas on disk
plan      →  configuration + state + provider refresh
apply     →  real API / runtime changes

Validate a Terraform configuration

The lab uses hashicorp/local so you can validate real configuration without cloud credentials. Create the directory once and stay in it for the rest of the article.

Prepare and initialize the lab

Create the working directory:

bash
mkdir -p ~/terraform-labs/terraform-validate && cd ~/terraform-labs/terraform-validate

Write a minimal root module that manages one local file:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "${path.module}/example.txt"
  content  = "validate ok"
}
EOF

terraform validate expects provider plugins in .terraform/. Initialize the directory:

bash
terraform init -input=false
output
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 error examples reuse one working directory and normally reuse the installed provider. Where the article deliberately removes .terraform/, initialization is repeated to demonstrate fresh-checkout and CI behavior.

Run terraform validate

With providers on disk, check the configuration:

bash
terraform validate
output
Success! The configuration is valid.

That single line is what you want before sharing a module or merging a pull request: Terraform parsed the files and found no schema or reference errors.

Why terraform init is required

Validate needs provider schemas from installed plugins. Remove the local install metadata to see what happens on a fresh checkout before init:

bash
rm -rf .terraform .terraform.lock.hcl

Run validate without reinitializing:

bash
terraform validate
output
╷
│ Error: Missing required provider
│
│ This configuration requires provider registry.terraform.io/hashicorp/local,
│ but that provider isn't available. You may be able to install it
│ automatically by running:
│   terraform init
╵

Restore the provider install before the error demos:

bash
terraform init -input=false

Errors terraform validate can detect

The subsections below overwrite main.tf in the same directory. Because the provider is already installed, each example needs only cat and terraform validate.

Syntax errors

A missing closing brace is one of the most common mistakes:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "${path.module}/example.txt"
  content  = "broken"
# missing closing brace
EOF

Run validate against the broken file:

bash
terraform validate
output
╷
│ Error: Unclosed configuration block
│
│   on main.tf line 12, in resource "local_file" "example":
│   12: resource "local_file" "example" {
│
│ There is no closing brace for this block before the end of the file. This
│ may be caused by incorrect brace nesting elsewhere in this file.
╵

Add the closing } for the resource block, or restore the good configuration from the lab setup section, before you continue.

Unsupported arguments

Provider schemas define which arguments each resource accepts. Add a fake attribute:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename            = "${path.module}/example.txt"
  content             = "test"
  not_a_real_argument = "oops"
}
EOF

The extra attribute is not in the local_file schema:

bash
terraform validate
output
╷
│ Error: Unsupported argument
│
│   on main.tf line 15, in resource "local_file" "example":
│   15:   not_a_real_argument = "oops"
│
│ An argument named "not_a_real_argument" is not expected here.
╵

Terraform matched your configuration against the local_file schema from hashicorp/local and rejected the unknown argument.

Invalid references

References must point at resources, data sources, or symbols Terraform can see in the same module:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "${path.module}/example.txt"
  content  = terraform_data.missing.output
}
EOF

terraform_data.missing was never declared in this module:

bash
terraform validate
output
╷
│ Error: Reference to undeclared resource
│
│   on main.tf line 14, in resource "local_file" "example":
│   14:   content  = terraform_data.missing.output
│
│ A managed resource "terraform_data" "missing" has not been declared in the
│ root module.
╵

Point content at a real value before you expect validate to pass.

Type mismatches

Schemas also constrain expression types. count must be a number, not a string:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  count    = "two"
  filename = "${path.module}/example.txt"
  content  = "test"
}
EOF

count must be a number, not a string:

bash
terraform validate
output
╷
│ Error: Incorrect value type
│
│   on main.tf line 13, in resource "local_file" "example":
│   13:   count    = "two"
│
│ Invalid expression value: a number is required.
╵

Not every type surprise fails at validate time. For example, content = 12345 on local_file passed validate in this lab because Terraform accepts that value for the attribute schema. When you need a hard failure before apply, use explicit type constraints or validation rules in a dedicated lesson on custom conditions.

The table below summarizes the error patterns you just reproduced, plus a few related messages you may see in other modules:

Symptom Likely cause Fix
Unclosed configuration block Missing } or broken nesting Close every block; run terraform fmt to expose structure
Unsupported argument Typo or argument not in provider schema Remove or rename the argument; check provider docs
Reference to undeclared resource Wrong resource name or missing declaration Declare the target or fix the reference path
Incorrect value type String where a number is required (or similar) Adjust the expression type to match the schema
Missing required provider terraform init not run in this directory Run terraform init (or init -backend=false in CI)
Missing required argument Required schema attribute omitted Add the argument the provider schema marks required
Success! but apply fails Valid HCL; runtime, permissions, or API limits Debug with terraform plan and provider error output

Use terraform validate in automation

CI jobs and scripts use the same validate command with flags that skip backend setup or emit JSON.

JSON output

Restore a valid configuration before you inspect machine-readable output:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "${path.module}/example.txt"
  content  = "validate ok"
}
EOF

The -json flag prints one JSON document on stdout:

bash
terraform validate -json
output
{
  "format_version": "1.0",
  "valid": true,
  "error_count": 0,
  "warning_count": 0,
  "diagnostics": []
}

Pipe through jq on Ubuntu when you want a quick field check:

bash
terraform validate -json | jq .

When validation fails, the same structure appears with valid set to false and populated diagnostics. Each entry includes severity, summary, detail, and source location fields (range.filename, range.start.line) suitable for CI parsers. Warnings may appear in diagnostics while valid remains true — only errors make the configuration invalid.

Validate without configuring a backend

Pull-request checks often run before credentials for a remote state bucket exist. A root module may declare a remote backend even though CI only needs to validate syntax:

bash
cat > main.tf <<'EOF'
terraform {
  backend "s3" {
    bucket = "example-tfstate-bucket"
    key    = "terraform-validate/lab/terraform.tfstate"
    region = "us-east-1"
  }
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "${path.module}/example.txt"
  content  = "ok"
}
EOF

A normal terraform init would configure the S3 backend and require access to that bucket. For validation-only jobs, clear prior init metadata and skip backend initialization:

bash
rm -rf .terraform .terraform.lock.hcl

Install providers without configuring the S3 backend:

bash
terraform init -backend=false -input=false
output
Initializing provider plugins...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)

Terraform has been successfully initialized!

For this validation-only workflow, -backend=false skips backend initialization while still installing the provider needed by terraform validate. Validation still works because it only needs schemas on disk:

bash
terraform validate
output
Success! The configuration is valid.

This pattern checks module syntax early in CI. It does not replace a full terraform plan against real state before production apply.

CI validation workflow

On a fresh checkout, run formatting, initialization, and validation in that order. Each step depends on the working directory state the previous step left:

bash
terraform fmt -check -recursive

fmt -check exits non-zero when files would change. Next install providers without touching the configured backend:

bash
terraform init -backend=false -input=false

Finally validate the configuration:

bash
terraform validate

terraform validate exits unsuccessfully when validation errors make the configuration invalid; warnings may be reported without making validation fail. Fail the job on a non-zero exit from any of the three commands. Wiring these into GitHub Actions, GitLab CI, or Jenkins is out of scope here.


terraform validate vs fmt vs plan

The three commands sit at different layers of the workflow:

Command Primary job Needs init Reads state Contacts providers for real infrastructure
terraform fmt Canonical HCL formatting No No No
terraform validate Configuration consistency Usually yes No No
terraform plan Proposed changes vs state Yes Yes Yes (refresh)

[terraform fmt](/terraform-fmt/) and terraform validate complement each other: fmt fixes spacing and layout; validate catches semantic mistakes fmt will never see.

You normally do not need to run terraform validate immediately before every terraform plan, because plan already performs an implied validation check. Use validate as a fast standalone editor or CI check; use plan when you need to validate in the context of a particular run, workspace, or input-variable values.

Why validation can pass while apply fails

Validation does not execute provider operations. Overwrite main.tf with a path the OS refuses to write:

bash
cat > main.tf <<'EOF'
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

provider "local" {}

resource "local_file" "example" {
  filename = "/proc/self/mem"
  content  = "cannot write here"
}
EOF

Confirm validate is still happy:

bash
terraform validate
output
Success! The configuration is valid.

terraform plan may still succeed because it only builds a change set. Apply is where the provider hits the filesystem:

bash
terraform apply -auto-approve -no-color
output
local_file.example: Creating...
╷
│ Error: Create local file error
│
│   with local_file.example,
│   on main.tf line 10, in resource "local_file" "example":
│
│ An unexpected error occurred while writing the file
│
│ Original Error: write /proc/self/mem: input/output error
╵

The configuration was syntactically valid; the runtime environment was not. That gap is why teams review plan output even after validate passes.


References


Summary

terraform validate is the quick gate between editing .tf files and running plan or apply.

On an initialized working directory it confirms that:

  • HCL parses
  • References resolve
  • Arguments match installed provider schemas

That is exactly what you want in local development and CI before anyone merges configuration changes.

The single lab under ~/terraform-labs/terraform-validate/ walked through successive main.tf overwrites for syntax, schema, reference, and type failures, deliberate re-init after removing .terraform/, JSON diagnostics, init -backend=false with a declared S3 backend, and a complete CI command sequence.

The /proc/self/mem example is the caveat to remember: validate proves consistency, not that a provider can complete an operation in your environment.

Pair validate with terraform fmt for formatting. You do not need validate immediately before every plan because plan includes an implied validation check — run plan when you need proposed changes against state.

Custom validation blocks, preconditions, and third-party policy tools are covered in Terraform validation checks for constraints beyond core validate.

When plan output looks right, continue with terraform apply to execute the approved change set.


Frequently Asked Questions

1. What does terraform validate do?

terraform validate checks whether a Terraform configuration is syntactically valid and internally consistent against installed provider and module schemas. It does not contact cloud APIs, read remote state, or prove that apply will succeed.

2. Do I need to run terraform init before terraform validate?

Yes for most real configurations. Validate needs provider plugins and module packages on disk, which terraform init downloads. Without init you may see Missing required provider even when the HCL itself looks fine.

3. What is the difference between terraform validate and terraform plan?

validate checks configuration consistency only. plan compares configuration and state through providers to propose infrastructure changes and performs an implied validation check. A configuration can pass validate and still fail plan or apply when credentials, state, or runtime conditions differ.

4. Can I run terraform validate without a remote backend?

Yes. terraform init -backend=false installs providers and modules without configuring or contacting the backend declared in your configuration, which is a common CI pattern before validate.

5. What does terraform validate -json return?

A JSON object with valid, error_count, warning_count, and diagnostics. Warnings can appear while valid remains true; only errors make validation fail.
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)