When HashiCorp refreshed the Terraform Associate exam to the 004 objectives, objective 4g was worded to cover the whole family of ways you can “validate configuration using custom conditions.” Most study material stops at the two most familiar members of that family — input variable validation blocks and resource precondition/postcondition blocks. But the objective’s linked tutorials also point at the newest member, one that behaves differently from every other validation tool in Terraform: the check block.
Because check blocks are new (Terraform v1.5+) and behave unlike the validations most people already know, they’re easy to confuse with the blocking validations — which is why the 004 objectives call them out explicitly under 4g. This guide covers check blocks from a practitioner’s perspective: the syntax, the crucial “warning, not error” behavior, scoped data sources, and — most importantly — a clear decision framework for when to reach for a check versus a precondition, postcondition, or variable validation. For the two validations check blocks build on, the variables, outputs, and locals guide covers input validation, and the meta-arguments and lifecycle guide covers precondition/postcondition; this article focuses on what’s genuinely different.
Where check Blocks Fit in the 004 Objectives
Objective 4g of the Terraform Associate (004) exam is “Validate configuration using custom conditions.” That umbrella spans three tools:
| Tool | Introduced | Attached to | On failure |
|---|---|---|---|
Input variable validation | 0.13 | An input variable | Blocks — error before plan |
precondition / postcondition | 1.2 | A resource, data source, or output lifecycle | Blocks — error during plan/apply |
check block | 1.5 | Nothing — it’s a top-level block | Warns — does not block |
The first two guarantee that Terraform refuses to proceed when an assumption is violated. The check block is the odd one out: it’s a top-level, standalone block that reports problems as warnings and lets the run continue. That single behavioral difference is the heart of what the exam wants you to understand, so let’s make it concrete.
The Anatomy of a check Block
A check block has a label (its name) and contains one or more assert blocks. Each assert has a boolean condition and an error_message:
check "health_check" {
assert {
condition = aws_instance.web.instance_state == "running"
error_message = "The web instance is not in the running state."
}
}
If the condition evaluates to false, Terraform prints the error_message as a warning and keeps going. Nothing is halted; no resource is prevented from being created or updated. Contrast that with a postcondition, where the same failed assertion would abort the operation.
You can group several assertions in one check, and you can have many check blocks in a module:
check "api_contract" {
assert {
condition = length(aws_lb.app.subnets) >= 2
error_message = "The load balancer must span at least two subnets for HA."
}
assert {
condition = aws_lb.app.load_balancer_type == "application"
error_message = "This service requires an Application Load Balancer."
}
}
When check Blocks Run: The Last Step
Timing is where check blocks diverge most from the other validations. Per the documentation, “the check block executes as the last step of a plan or apply operation, after Terraform has planned or provisioned your infrastructure.”
Read that carefully, because the timing differences are a common point of confusion:
- Variable
validationruns before planning — it guards inputs at the door. preconditionruns during plan, before a resource is evaluated — it guards assumptions going in.postconditionruns after a resource or data source is read/created — it guards guarantees coming out.checkruns last, after everything else has been planned or applied — it observes the resulting system as a whole.
Because it runs last and only warns, a check never prevents a deployment. It’s designed to surface drift or contract violations without becoming a gate that blocks your pipeline — the opposite of the “fail fast and stop” role that preconditions and validation play.
Scoped Data Sources: The Real Power Feature
A plain assertion can only reference values Terraform already has in its graph. The feature that makes check blocks genuinely useful is the scoped data source — a data block declared inside the check block:
check "api_health" {
data "http" "app_health" {
url = "https://${aws_lb.app.dns_name}/health"
}
assert {
condition = data.http.app_health.status_code == 200
error_message = "The application health endpoint returned ${data.http.app_health.status_code} instead of 200."
}
}
A scoped data source has three important properties:
- It’s evaluated only when the check runs — as part of that final step, after the rest of your infrastructure is planned or applied. That means it can query resources this very run just created.
- It exists solely to support the assertion. You can’t reference a scoped data source from anywhere outside its
checkblock. - Its errors also become warnings. If the scoped data source itself fails (say the endpoint is unreachable), Terraform reports a warning rather than failing the run — consistent with the non-blocking philosophy.
This is what enables post-apply health checks: apply your infrastructure, then, as the last step, actually call the endpoint you just provisioned and warn if it isn’t healthy. No other validation construct can do that, because the others run too early or would hard-fail the apply.
A Worked Example: Health-Checking a Deployment
Putting the pieces together, here’s the pattern HashiCorp’s own “Use Checks to Validate Infrastructure” tutorial demonstrates — validate that a web application behind a load balancer actually responds after you deploy it:
check "response" {
data "http" "app" {
url = "https://${aws_lb.app.dns_name}"
insecure = true
}
assert {
condition = data.http.app.status_code == 200
error_message = "Application returned ${data.http.app.status_code} instead of 200."
}
}
Walk through what actually happens on terraform apply:
- Terraform plans and provisions everything else first — the load balancer, target group, instances, and so on.
- As the last step, it evaluates the
checkblock: the scopeddata "http" "app"fires a real request to the load balancer’s DNS name that this run just created. - If the assertion passes, Terraform records the result silently in the state file’s
check_resultsfield and prints nothing — passing checks produce no output. - If the assertion fails (say the app returns a 502 while it’s still warming up), Terraform prints a warning with your
error_message, records the failed result in state, and the apply still succeeds. You are told, not stopped.
That last point is the whole reason check blocks exist: a postcondition on the load balancer would have failed the entire apply and left you unable to complete the deploy, even though the 502 is often transient. The check surfaces the problem for a human (or a subsequent health assessment) to judge, without holding the pipeline hostage to a flaky, point-in-time probe. This is also why you keep the condition cheap — it runs on every plan and apply.
Continuous Validation
Because check blocks re-evaluate on every plan and apply, they enable continuous validation — asserting that your infrastructure still meets its contract over time, not just at creation. In HCP Terraform (Terraform Cloud), checks power the continuous validation / health assessments feature: the platform periodically re-runs your checks against real infrastructure and flags when an assertion starts failing, so you learn that a certificate is about to expire or an endpoint went unhealthy before a customer does. For the exam logistics, cost, and a study plan around these objectives, the Terraform Associate exam guide is a useful companion (note it is written against the 003 objective numbering; this article follows the 004 content list, where custom conditions live under objective 4g).
check vs precondition vs postcondition vs validation
This is the distinction objective 4g centers on. Internalize the decision framework:
| You want to… | Use | Because |
|---|---|---|
| Reject a bad input value before anything runs | Variable validation | Fails fast at the door, blocking plan |
| Guarantee an assumption before a resource is used (e.g., the AMI is x86_64) | precondition | Blocks the operation if the assumption is wrong |
| Guarantee a result after a resource is created (e.g., the instance got a public IP) | postcondition | Blocks and surfaces the failure at the source |
| Monitor the deployed system without blocking (e.g., is the endpoint healthy?) | check block | Warns and continues; can use scoped data sources; runs last |
A one-line rule of thumb that survives most exam questions: if a failed assumption should stop the deployment, use a precondition/postcondition or variable validation; if you want to be told something is wrong but not halt the run, use a check. The word “warn,” “monitor,” “notify,” “continuous,” or “without blocking” in a question is a strong signal for check; “must,” “reject,” “prevent,” or “fail” points to the blocking validations.
Practical Guidance and Gotchas
checkblocks don’t fix anything. They observe and warn. Don’t reach for acheckwhen the correct behavior is to stop a broken apply — that’s aprecondition/postconditionjob.- A scoped data source can’t be shared. If two checks both need to hit the same endpoint, each declares its own scoped data source. That’s by design — scoped data sources are private to their
check. - Warnings can be missed in CI. Since checks never fail the run, a failing check won’t turn your pipeline red on its own. If you need enforcement, pair checks (for observability) with blocking validations or an external policy tool.
- Keep conditions cheap and deterministic. Because checks run on every plan and apply, an expensive or flaky scoped data source adds latency and noise to every run.
- Version awareness.
checkblocks require Terraform 1.5 or later. On the exam, if a scenario pins an older Terraform version, checks aren’t available and you’re back to preconditions/postconditions.
How check Blocks Show Up on the Exam
Expect objective 4g questions that hand you a small requirement and ask you to pick the right validation construct, or that show a check block and ask what happens when its assertion fails. The reliable answers:
- A failed
checkassertion → a warning; the apply still succeeds. - A failed
precondition/postconditionor variablevalidation→ an error; the operation stops. - “Validate an endpoint after apply” or “continuously verify infrastructure health” →
checkblock with a scoped data source. - “Ensure a variable is one of an allowed set before running” → variable
validation.
Get those four reflexes solid and objective 4g’s custom-condition questions become reliable points. To turn the concepts into exam-day speed, the HashiCorp Terraform Associate (004) Mock Exam Bundle includes custom-condition scenarios that pit check blocks against preconditions, postconditions, and variable validation — with explanations that surface the distinctions between them. If you want the foundations first, the core workflow walkthrough and the state management guide set the stage for where validation fits in a real Terraform run.
Conclusion
check blocks are the newest and most misunderstood member of Terraform’s custom-conditions family, and objective 4g of the 004 exam expects you to know how they differ. The whole story fits in one sentence: preconditions, postconditions, and variable validation block a run when an assumption is wrong; check blocks run last, warn instead of fail, and can use scoped data sources to continuously verify that your deployed infrastructure still meets its contract. Keep that boundary crisp — blocking versus non-blocking, early versus last, guard versus monitor — and both the exam questions and your real-world validation strategy fall neatly into place.
Frequently Asked Questions
What is a Terraform check block?
A top-level block (Terraform 1.5+) that contains assert blocks and optional scoped data sources. It runs as the last step of a plan or apply and reports failed assertions as warnings without blocking the operation.
How is a check block different from a precondition?
A precondition is attached to a resource, data source, or output and blocks the run with an error when its assumption fails. A check block is standalone, runs last, and only warns — the run continues regardless.
Do failed check assertions stop terraform apply?
No. A failed check assertion produces a warning and the apply still completes. If you need to halt the run on failure, use a precondition/postcondition or input variable validation instead.
What is a scoped data source in a check block?
A data block declared inside a check. It’s evaluated only when the check runs (as the final step), can query infrastructure the run just created, exists only to feed the assertion, and turns its own errors into warnings.
Are check blocks on the Terraform Associate 004 exam?
Yes. Objective 4g, “Validate configuration using custom conditions,” covers the custom-conditions family, and its study resources include using checks to validate infrastructure alongside variable validation and preconditions/postconditions.
When should I use check blocks over the blocking validations?
Use check blocks for monitoring and continuous validation — verifying deployed infrastructure health without blocking deployments. Use variable validation and preconditions/postconditions when a violated assumption should stop the run.
Want to nail objective 4g and the rest of the “Read, generate, and modify configuration” domain? Practice with realistic, explanation-backed questions in the Terraform Associate (004) mock exam bundle and make the blocking-vs-non-blocking distinction automatic before test day.