Skip to content
Quasar Tools Logo

What Does Terraform Validate Actually Check?

A precise breakdown of what terraform validate does and does not check — HCL syntax, schema rules, provider constraints, and what only terraform plan can catch.

DH
Tutorials & How-Tos12 min read2,700 words

terraform validate is one of the first commands Terraform users learn, but it is also one of the most misunderstood. It does not connect to any cloud provider. It does not check whether your resource values are valid in the real world. It does not look at your state file. What it does is fast, safe, and essential — but understanding exactly where it stops is the difference between a reliable CI pipeline and a false sense of security before terraform apply.

0API calls madefully offline static analysis
3Check categoriessyntax, schema, references
< 2sTypical run timeon most real-world configs

What terraform validate is

terraform validate is a built-in Terraform subcommand that performs static analysis on your configuration files. It reads every .tf and .tfvars file in the current working directory — and recursively any local modules — parses them, and checks whether the configuration is internally consistent and structurally valid.

Static analysis, not execution

The key characteristic of terraform validate is that it is entirely static. No network connections are made, no provider APIs are called, and no state file is read. The check happens entirely in memory on the machine running the command. This makes it safe to run in any environment — including CI runners that have no cloud credentials — and it completes in under two seconds on most real-world configurations.

This stands in contrast to terraform plan, which performs all the same static checks and then connects to provider APIs to compute a diff against real infrastructure. Validate is the lightweight first gate; plan is the comprehensive pre-apply gate. Running both in sequence gives you the broadest coverage before you commit to any infrastructure change.

Note

terraform validate was significantly improved in Terraform 0.12, when HCL2 became the configuration language. Before 0.12, validation was shallow and many structural errors only surfaced at plan time. Since 0.12, validate has a full type system and schema awareness, making it substantially more useful as a standalone check.

The three modes of operation

  • Default mode — runs after terraform init; checks syntax, schema against downloaded provider plugins, and cross-file references
  • Without init — if no .terraform directory exists, validate still runs but skips provider schema checks, reporting only HCL parse errors and reference issues it can resolve without provider metadata
  • JSON mode (-json) — emits a structured JSON object with a valid boolean, error_count integer, and a diagnostics array suitable for CI parsing and editor integrations

What terraform validate actually checks

Understanding the three categories that terraform validate covers helps you know exactly what passing validation guarantees — and where that guarantee ends.

1. HCL syntax correctness

The first pass is a parse of every .tf file for valid HCL2 syntax. This catches unclosed braces, missing equals signs in attribute assignments, invalid block definitions, incorrect use of heredoc syntax, and any other construct that is not valid HCL. A file that fails this check cannot be read by Terraform at all — plan and apply would both fail too. Validate catches these errors immediately with the file path and line number.

2. Provider schema conformance

After parsing, validate checks every resource block, data source block, and provider configuration against the schema defined by the relevant provider plugin. Schemas specify which arguments are valid, which are required vs optional, and what type each argument expects (string, number, bool, list, map, object). Validate will catch an argument that does not exist for a resource type, an argument set to the wrong type (e.g. passing a string where a number is required), and a required argument that is missing entirely.

Tip

Before validate can check provider schemas, you need to run terraform init in the working directory. Init downloads the provider plugins and stores them in the .terraform directory. Without that, validate has no schema to check against and will skip resource-level validation. The standard CI workflow is always init → validate → plan.

3. Internal reference validity

The third category is cross-reference checking within the configuration. Terraform configurations regularly reference other resources, variables, locals, module outputs, and data sources by name. Validate checks that every reference (e.g. var.region, local.common_tags, aws_vpc.main.id, module.networking.subnet_ids) points to something that is actually declared somewhere in the configuration. An undeclared variable, a typo in a resource reference, or a missing module output will all be caught here.

Check categoryExample errorRequires init?
HCL parse errorUnclosed brace on line 14No
Unknown argument"region" is not a valid argument for aws_s3_bucketYes
Wrong argument typeInappropriate value for attribute — expected numberYes
Missing required argumentThe argument "bucket" is requiredYes
Undeclared variableA managed resource can only refer to declared variablesNo
Undeclared resource refReference to undeclared resource "aws_vpc.typo"No
Missing module inputThe argument "vpc_id" is required for module.networkYes

What terraform validate does not check

The boundaries of terraform validate are as important as what it covers. Many developers learn these limits after a validate-passing configuration fails at apply time. Each of these categories requires either terraform plan, integration tests, or policy tools like tflint or Checkov.

Real resource argument values

Validate checks that an argument exists and has the right type — but it cannot check whether the value is valid in the real world. An aws_instance resource can have an ami argument that is a string (type-correct), but validate has no way to know whether that specific AMI ID exists in your AWS account or region. An invalid AMI, a non-existent security group ID, or an incorrect availability zone name will all pass validate and only fail at plan or apply.

State file and existing infrastructure

Validate never reads your Terraform state file. It cannot detect that a resource you are defining conflicts with one that already exists, that a resource was deleted outside of Terraform (state drift), or that a planned change violates a constraint that can only be evaluated against current real infrastructure. These are all plan-time and apply-time concerns.

Dynamic expressions that depend on data sources

count, for_each, and conditional expressions are valid HCL and validate parses them without issue. But if their values depend on a data source (e.g. for_each = toset(data.aws_availability_zones.available.names)), the expression cannot be fully evaluated at validate time because the data source has not been queried. Validate confirms the expression syntax is correct; it cannot confirm the runtime outcome.

Provider authentication and permissions

Validate makes zero API calls. It will not detect that your AWS credentials are expired, that your service account lacks the required IAM permissions, or that your provider configuration points to the wrong region or project. All authentication errors only surface at plan or apply time when the provider client is actually initialized and calls are made.

Warning

Passing terraform validate does not mean your configuration is ready to apply. It means your configuration is syntactically correct and schema-valid. Always follow validate with terraform plan — in a staging environment if possible — before running terraform apply in production.

Security policy and compliance rules

Validate has no concept of security policy. An S3 bucket configured as public, an EC2 instance without encryption, or a security group with 0.0.0.0/0 ingress on port 22 will all pass validate without any warning. Security and compliance checks require dedicated policy tools like Checkov, tfsec, or HashiCorp Sentinel.

terraform validate vs terraform plan

The most frequent source of confusion about terraform validate is how it differs from terraform plan. They overlap significantly but operate at different levels — and both are necessary for a complete pre-apply workflow.

terraform validate checks whether a configuration is syntactically valid and internally consistent, regardless of any provided variables or existing state.

HashiCorp Terraform documentation

Where they overlap

Both commands parse your HCL files and check for syntax errors. Both check provider schemas when provider plugins are available. Both validate internal references. A configuration error that terraform validate catches would also be caught by terraform plan — validate is simply faster and does not require cloud credentials or a state backend.

Where plan goes further

terraform plan initializes provider clients, authenticates with cloud APIs, reads the current state, and queries data sources. This enables it to catch things validate cannot: a resource argument value that the provider API rejects, a data source query that returns unexpected results, quota or rate-limit errors, and conflicts between the proposed configuration and the existing infrastructure tracked in state.


Capabilityterraform validateterraform plan
HCL syntax check✓ Yes✓ Yes
Provider schema check✓ Yes (after init)✓ Yes
Cross-reference check✓ Yes✓ Yes
Real resource value check✗ No✓ Yes (via API)
State file read✗ No✓ Yes
Data source query✗ No✓ Yes
Auth/permissions check✗ No✓ Yes
Security policy check✗ No✗ No (needs tfsec/Checkov)
Requires cloud credentials✗ No✓ Yes
Typical run time< 2s5s – several minutes

Note

In CI pipelines, validate and plan serve different purposes. Run validate on every pull request commit — it is fast, credential-free, and catches the majority of authoring mistakes early. Run plan in a separate job that has access to staging credentials, triggered on merge or as a manual approval step.

How to run terraform validate

Running terraform validate is straightforward, but the steps around it matter for getting the most out of the command.

1

Run terraform init to download providers

In your Terraform working directory, run terraform init. This downloads the provider plugins defined in your required_providers block and stores them in the .terraform subdirectory. Without init, validate skips provider schema checks and only performs HCL parse and reference validation. Use terraform init -backend=false in CI to skip remote state configuration when credentials are not available.

2

Run terraform validate

Execute terraform validate in the same directory. The command exits with code 0 (pass) or code 1 (fail). On success, it prints "Success! The configuration is valid." On failure, it prints each error with the file path, line and column number, and a description. Use terraform validate -json for structured output in CI scripts.

terminal
bash
# Basic validation
terraform validate

# JSON output for CI parsing
terraform validate -json

# Example JSON output structure
{
  "valid": false,
  "error_count": 2,
  "diagnostics": [
    {
      "severity": "error",
      "summary": "Unsupported argument",
      "detail": "An argument named 'regoin' is not expected here. Did you mean 'region'?",
      "range": {
        "filename": "main.tf",
        "start": { "line": 8, "column": 3 }
      }
    }
  ]
}
3

Review and fix reported errors

Each diagnostic includes a file path and line number. Open the flagged file and look at the reported line plus the 3–5 lines above it — HCL errors sometimes surface slightly after the actual mistake. Common fixes include correcting argument names (typos are the most frequent), providing a missing required argument, fixing a type mismatch (quoting a number that should be unquoted), or declaring a variable that is referenced but not defined.

4

Follow up with terraform plan

Once validate passes cleanly, run terraform plan in an environment with valid credentials. This is the second gate that catches runtime issues validate cannot see — invalid resource values, permission errors, and infrastructure state conflicts. The two commands together cover the full pre-apply validation surface.

HCL Formatter

Normalize HCL indentation, block spacing, and attribute alignment across your Terraform files before running validate — browser-local, no signup required.

Open tool

terraform validate in CI/CD

terraform validate is a natural fit for CI pipelines because it requires no cloud credentials, runs in seconds, and catches most authoring mistakes before they waste a plan execution or reach a code review. The standard pattern is to run it on every pull request that modifies .tf files.

GitHub Actions example

The workflow below installs Terraform, runs init with -backend=false to avoid needing state credentials, and runs validate. If validate fails, the workflow exits with a non-zero code and blocks the pull request from merging.

.github/workflows/terraform-validate.yml
yaml
name: Terraform Validate

on:
  pull_request:
    paths:
      - '**.tf'
      - '**.tfvars'

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: '1.8.0'

      - name: Terraform Init (no backend)
        run: terraform init -backend=false

      - name: Terraform Validate
        run: terraform validate -json | tee validate-output.json
        # Exit code 1 on any error — fails the workflow automatically

Tip

Using -backend=false means terraform init downloads provider plugins without attempting to initialize the remote state backend. This is the correct pattern for pull request CI jobs that should not touch the shared state file and have no backend credentials.

Combining validate with tflint

tflint is a linter that catches issues terraform validate misses — provider-specific rule checks (like invalid AWS instance types), unused declarations, and custom policy rules. Running tflint after validate in the same CI job gives broader static analysis coverage. tflint has provider-specific rule plugins for AWS, Azure, and GCP that check argument values against known valid options, catching errors that validate's generic schema check cannot.

  • terraform fmt -check — verify code is formatted per Terraform style conventions (fails if any file needs reformatting)
  • terraform validate — check syntax, schema conformance, and internal references
  • tflint — provider-specific rules, unused variable detection, custom policy enforcement
  • Checkov or tfsec — security and compliance policy scanning
  • terraform plan — runtime validation in a staging environment with real credentials

Terraform Resource Naming Convention Checker

Validate Terraform resource, module, variable, and output labels for consistent naming style and policy compliance — runs entirely in your browser.

Open tool

Best practices for full validation

terraform validate is a foundation, not a ceiling. A mature Terraform workflow layers multiple validation techniques to catch different classes of error at the right point in the development cycle.

Format before you validate

Run terraform fmt before validate in every workflow — local and CI. Canonical HCL formatting is not just style; it prevents edge cases where inconsistent whitespace or comment placement obscures real errors in parse output. The HCL Formatter on Quasar Tools provides the same normalization in your browser without needing Terraform installed — useful for quick reviews or editing on machines where you cannot run terraform fmt.

Always init before validate in CI

Running validate without init gives you only partial analysis — HCL parsing and reference checks, but no provider schema validation. Skipping schema checks means you can merge a configuration that uses an argument name with a typo or passes the wrong type to a resource attribute. The extra few seconds that init -backend=false adds to the CI job are worth the coverage.

Use -json for structured CI output

The default human-readable output from validate is clear for local debugging, but JSON output is far more useful in automated pipelines. With -json, you can parse the diagnostics array to extract file paths and line numbers, annotate pull request diffs with inline error comments using the GitHub Checks API, or feed errors into a custom Slack notification. Parse the valid boolean first — if it is true, the diagnostics array may still contain warnings worth surfacing.

Validate all modules independently

terraform validate in a root module also checks called local modules, but remote modules are only checked after init downloads them. For module repositories, run validate separately in each module directory during development. This surfaces schema errors in the module itself before consumers ever reference it.

Warning

A common misconception is that terraform validate covers security. It does not. A perfectly valid Terraform configuration can provision publicly-accessible storage buckets, unencrypted databases, or overly permissive IAM roles. Always run Checkov, tfsec, or a similar policy scanner as a separate step after validate in your CI pipeline.

Keep .tf files formatted before committing

Use a pre-commit hook that runs terraform fmt -check and fails if any .tf file is not canonically formatted. This keeps the entire codebase consistent, avoids style-only diffs in code reviews, and ensures validate output is easier to read because the code has clean structure. Strip development comments from production configs using the Terraform HCL Comment Remover to keep committed files clean and readable.

Key takeaways

  • terraform validate checks HCL syntax, provider schema conformance, and internal cross-references — it makes zero API calls and requires no cloud credentials.
  • You must run terraform init before validate to enable provider schema checks; without it, validate skips resource argument validation.
  • Validate cannot catch invalid resource argument values, state drift, missing permissions, or security policy violations — those require terraform plan and dedicated policy tools.
  • The -json flag outputs structured diagnostics (valid, error_count, diagnostics[]) ideal for CI parsing, inline PR annotations, and custom reporting pipelines.
  • The correct CI workflow is: terraform fmt -check → terraform init -backend=false → terraform validate → tflint → terraform plan (in staging with credentials).
  • Use the HCL Formatter and Terraform Resource Naming Convention Checker for pre-validate style and naming hygiene.
  • Passing terraform validate does not mean the configuration is ready to apply — it means it is syntactically and structurally correct enough to proceed to plan.

Frequently Asked Questions

terraform validate checks three things: HCL syntax correctness (valid blocks, correct attribute syntax, no parse errors), schema conformance (whether the attributes you used are recognized by the provider and set to compatible types), and internal reference validity (whether every variable, local, module output, and resource reference is declared somewhere in the configuration). It does not connect to any provider API, so it cannot verify whether a resource with those arguments actually exists or can be created.

Yes, for full validation. Running terraform init downloads the provider plugins and their schema definitions — without them, terraform validate cannot check whether the arguments you used are valid for a given resource type. Since Terraform 0.13, running validate without init produces an error for configurations that reference providers. If you want to skip provider schema checks entirely (e.g. in a pure syntax check), use the -no-color flag and accept the limitation, but init-then-validate is the standard workflow.

terraform validate is a pure static analysis step — it reads your .tf files, checks syntax and schema, and exits without contacting any provider API. terraform plan performs all those same checks and then connects to the provider APIs to determine what changes would actually be made. Plan catches issues validate cannot: invalid resource argument values (like a region name that does not exist), missing permissions, quota limits, and state drift. Validate is fast and safe for CI; plan requires credentials and real infrastructure access.

No. terraform validate catches structural and schema errors but misses a significant class of runtime errors. It will not catch an invalid AMI ID in an AWS instance, a GCS bucket name that violates character rules, or a resource that conflicts with an existing one in state. It also does not validate the logic of count and for_each expressions if they depend on data sources or remote values. Think of validate as a fast first gate — necessary but not sufficient for full pre-apply confidence.

Add a CI job that runs terraform init followed by terraform validate. Use the hashicorp/setup-terraform action to install the correct Terraform version, then run init with -backend=false to skip remote state initialization (which requires credentials), followed by validate. The validate step exits with a non-zero code on any error, which fails the workflow and blocks the pull request. This pattern catches HCL syntax and schema errors on every commit without requiring cloud credentials in the CI environment.

The -json flag makes terraform validate emit a structured JSON object instead of human-readable text. The object has a valid boolean (true or false), an error_count integer, and a diagnostics array. Each diagnostic entry includes a severity (error or warning), a summary message, a detail string, and a range object with filename, start line, and end line. This format is ideal for parsing in CI scripts, integrating with custom reporting dashboards, or feeding into editor extensions that display inline annotations.

Yes, partially. terraform validate checks that module call blocks reference a module source that exists locally or can be resolved, that required input variables for the module are provided, and that the types passed to module inputs are compatible with the variable declarations in the module. It does not fetch remote modules at validate time unless they were already downloaded by terraform init. After init, the downloaded module source is available locally and can be fully schema-checked.

A complete Terraform quality workflow layers several tools. terraform validate covers syntax and schema. terraform plan covers runtime behavior. tflint adds provider-specific rule checks and custom policy rules beyond what validate enforces. Checkov and tfsec perform security policy scanning. For HCL formatting and style consistency before any of these run, the HCL Formatter on Quasar Tools normalizes indentation and block structure, and the Terraform Resource Naming Convention Checker validates label naming against your team's conventions.

ShareXLinkedIn

Related articles