What does `terraform validate` actually check, and why can a configuration pass validate and still fail during apply?
answer
- internal consistency, not reality
- schemas come from an installed provider
- no API calls, no state, no credentials
- init -backend=false makes it credential-free
- unset variables are just unknown
basics
~20 sterraform validate checks a configuration against itself and the installed provider schemas: syntax, references, required arguments, attribute names and type consistency. It contacts no provider API and reads no state, so permissions, quotas and real-world values only surface at plan or apply.
solid answer
~50 s`terraform validate` is an offline, internal-consistency check. It parses the configuration and cross-checks it against the schemas of the providers and modules that `terraform init` installed: are referenced variables and resources declared, do the attribute names exist on that resource type, are required arguments present, do the types line up. It explicitly does not access remote services — no provider API calls, no state, no credentials — which is why it needs an initialized directory but not a working AWS session. You do not have to supply variable values either; unset inputs are treated as unknown. That boundary explains the apply-time failures it can never see: an instance type that does not exist in the region, a bucket name already taken, an IAM denial, a quota, a value the provider rejects at request time. In CI the usual pattern is `terraform init -backend=false` followed by `terraform validate`, so the check runs with no cloud access at all.
code
bash · 6 lines# Credential-free validation in CI: install providers, skip the backend
terraform init -backend=false -input=false
terraform validate -json > validate.json
# valid=false means at least one diagnostic; each carries a file and line range
cat validate.jsongo deeper
Be able to say that validate checks the configuration for syntax and reference errors and must be run after terraform init, and that it does not talk to the cloud.
Explain where the provider schema comes from and why that forces an init, then give concrete examples of apply-time failures — quotas, IAM denials, a nonexistent instance type — that validate structurally cannot see.
Demonstrate the credential-free CI shape with init -backend=false, and be ready to justify what you place after validate to cover security and provider-specific value rules it ignores.
Own the access model this implies: which checks run on untrusted contributions with no cloud credentials at all, and where the pipeline first has to hand out a role — that boundary is a security decision, not a tooling preference.
## What validate is for `terraform validate` answers one question: is this configuration internally coherent? It is the second-cheapest check in the Terraform toolchain, sitting just above `terraform fmt -check`, and it is the last one that costs nothing and touches nothing. Concretely it catches: - syntax that parses but does not make sense — a reference to `var.regionn`, an expression pointing at a resource address that is not declared; - schema violations — an argument name that does not exist on that resource type, a required argument that is missing, a block used where the schema expects an attribute; - type inconsistencies — passing a list where the declared type is a string, or a module input whose type constraint does not accept the value being wired in; - duplicate declarations — two resources with the same type and name, two variables with the same name. ## Why it needs `terraform init` first Most of the above is impossible without provider schemas. Terraform has no built-in knowledge that `aws_instance` has an `instance_type` argument; that comes from the AWS provider plugin. So validate requires an initialized working directory: the providers must be downloaded into `.terraform` and any child modules must be installed. Run it in a fresh clone and it tells you to run `init` first. This creates an awkward-looking dependency — an offline check that requires a download step — and the standard resolution is: ```bash terraform init -backend=false terraform validate ``` `-backend=false` skips backend initialization, so the job never reaches for the remote state bucket and never needs credentials. Providers are still installed, so schema checking works. This is the shape you want for a validate job that runs on a fork's pull request, where handing out cloud credentials would be a bad idea. ## What it deliberately cannot see Validate never talks to a provider's API. Everything that depends on the real world therefore passes validation and fails later: - an EC2 instance type that is valid syntax but does not exist in that region; - an S3 bucket name that is globally taken; - an IAM policy the API rejects as malformed, or a role your credentials are not allowed to create; - a service quota you have already exhausted; - a value the provider validates only in its own `ValidateFunc` at plan time, or that the API validates at apply time. It also does not read state, so it knows nothing about drift, about what already exists, or about whether the change would be a destroy. And because unset variables are treated as unknown rather than as an error, a configuration can validate cleanly and then fail plan with "No value for required variable". The useful mental model: **validate checks the code, plan checks the code against reality.** ## The `-json` output `terraform validate -json` emits a machine-readable document with a `valid` boolean and a `diagnostics` array carrying severity, summary, and the file and line range for each finding. That is what you feed to a pipeline annotator so failures land on the right lines of the pull request rather than in a log nobody opens. ## Where it sits against the other checks Validate is not a security tool and not a policy tool. It will happily bless a security group open to `0.0.0.0/0` on port 22, an unencrypted bucket, and a hardcoded secret — all of those are perfectly well-formed configuration. It is also not a best-practice linter: an unused variable, a deprecated idiom, or a nonexistent instance type are all outside its remit, which is where TFLint's rulesets come in. The cheap-to-expensive ordering that follows from all this is: format check, then validate on an init'd-but-backend-less directory, then lint and security scanning, then a real `plan` with credentials. Each layer is slower and needs more access than the one before, and each can only find what the previous layers let through. ## Answering it well The strong answer names the boundary rather than listing checks: validate is scoped to the configuration plus the installed schemas, with no network calls to any provider. Everything you can say about what it catches and what it misses follows from that one sentence.
- A configuration passes `terraform validate` in CI but the very next step, `terraform plan`, fails with "No value for required variable". Why did validate not catch that?Validate treats unset input variables as unknown values rather than as errors, so a configuration with no tfvars still validates. Requiring a value is a plan-time concern, because only plan has to produce concrete attribute values. If you want that caught earlier, supply the variables to the validate step or check the presence of the tfvars file explicitly.
- What does `terraform validate -json` give you that the human-readable output does not?A structured document with a `valid` boolean and a `diagnostics` array, each entry carrying severity, a summary, and the file and line range. Pipelines use it to annotate the exact lines of a pull request instead of dumping text into a job log, and to distinguish warnings from errors programmatically.
- Does `terraform validate` say anything about whether a configuration is secure?No. An unencrypted bucket, a security group open to the world, and a hardcoded credential are all perfectly well-formed configuration, and validate blesses them. Security and compliance rules come from dedicated scanners such as Checkov or Trivy, run over the HCL or over the JSON plan; validate's remit stops at schema and reference correctness.
saying these in an interview costs you the question
- Says validate contacts the cloud provider to check resources
- Thinks validate runs a plan or compares against state
- Claims validate catches security misconfigurations
- Expects validate to work in a fresh clone without init
- Believes validate errors on required variables that have no value