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.
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
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
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 category | Example error | Requires init? |
|---|---|---|
| HCL parse error | Unclosed brace on line 14 | No |
| Unknown argument | "region" is not a valid argument for aws_s3_bucket | Yes |
| Wrong argument type | Inappropriate value for attribute — expected number | Yes |
| Missing required argument | The argument "bucket" is required | Yes |
| Undeclared variable | A managed resource can only refer to declared variables | No |
| Undeclared resource ref | Reference to undeclared resource "aws_vpc.typo" | No |
| Missing module input | The argument "vpc_id" is required for module.network | Yes |
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
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.
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.
| Capability | terraform validate | terraform 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 | < 2s | 5s – several minutes |
Note
How to run terraform validate
Running terraform validate is straightforward, but the steps around it matter for getting the most out of the command.
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.
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.
# 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 }
}
}
]
}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.
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.
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.
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 automaticallyTip
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.
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
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.