Skip to main content

Terraform Validate and Tflint

terraform validate checks that a Terraform configuration is syntactically correct and internally consistent — catching errors before plan or apply. Tflint is a Terraform linter that catches additional issues like deprecated syntax, missing required provider version constraints, and provider-specific rule violations.

What Is terraform validate?

terraform validate checks that your Terraform files are syntactically correct HCL and internally consistent — meaning every resource reference points to something that actually exists in the configuration — without needing any cloud credentials at all. It is the fastest possible check you can run, because it never talks to AWS, GCP, or any API.

Think of it as a spell-checker for your Terraform code. It catches typos in block syntax, missing required arguments, and references to undeclared variables. What it cannot tell you is whether AWS will actually accept the values you have given it — that is what plan is for.

What Is Tflint?

Tflint goes a step further than validate — it is a dedicated linter that understands provider-specific rules. validate will happily pass a configuration that uses a deprecated EC2 instance type or forgets a recommended tag, because that is not a syntax error. Tflint catches exactly these kinds of "technically valid, practically wrong" issues.

At PhonePe, the CI pipeline runs both tools in sequence on every pull request, before anything resembling a real terraform plan against AWS credentials happens. This catches roughly 80% of the obviously broken PRs — a missing closing brace, a renamed variable nobody updated, a deprecated AWS instance family — in under five seconds, instead of waiting two minutes for a full provider-authenticated plan to fail with the same error.

Validate vs Plan — Knowing the Difference

Bash
terraform validate terraform plan
- Runs OFFLINE, no credentials needed - Needs real cloud credentials
- Checks syntax and internal refs - Checks against REAL infrastructure
- Takes under 1 second - Takes seconds to minutes
- Catches: typos, missing arguments, - Catches: invalid AMI IDs, IAM
undeclared variable references permission errors, quota limits

Running terraform validate

Bash
terraform init -backend=false # skip backend setup — validate doesn't need state
terraform validate
# Success output:
# Success! The configuration is valid.
# Failure output (an actual undeclared variable reference):
# Error: Reference to undeclared input variable
# on main.tf line 12, in resource "aws_instance" "app":
# 12: instance_type = var.intsance_type
# An input variable with the name "intsance_type" has not been declared.
# ^^^^^^^^^^^^^^^^ typo — should be instance_type

What validate Catches vs What It Misses

Common mistake

Common mistake: assuming a clean terraform validate means your code is correct. It only proves the HCL is well-formed. It will not catch an AMI ID that does not exist in your target region, an IAM policy that grants no actual permissions, or a CIDR block that overlaps with an existing subnet — all of those only surface during plan or apply, because they require real cloud-side knowledge.

Installing and Configuring Tflint

Bash
# Install tflint (macOS/Linux via the official install script)
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
# Initialize tflint in your project — downloads any configured plugins
tflint --init
HCL
# .tflint.hcl — configuration file at the repo root
plugin "aws" {
enabled = true
version = "0.31.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_required_version" {
enabled = true # forces every module to declare required_version
}
rule "terraform_required_providers" {
enabled = true # forces every module to pin its provider versions
}

Running Tflint with the AWS Plugin

Bash
tflint
# Example output catching a deprecated instance type:
# main.tf
# 12:12 warning "t2.micro" is previous generation. Consider using "t3.micro" instead.
# (aws_instance_previous_type)
#
# 18:3 error "ap-south1" is an invalid value for region. (aws_instance_invalid_ami)
# typo caught — should be "ap-south-1"

Integrating Both into a CI Pipeline

YAML
# .github/workflows/terraform-lint.yml
- name: Terraform Format Check
run: terraform fmt -check -recursive
- name: Terraform Validate
run: |
terraform init -backend=false
terraform validate
- name: Tflint
run: |
tflint --init
tflint --recursive
Note

Tip from a senior engineer: order matters in CI. Run fmt -check, then validate, then tflint, then only after all three pass, run the real authenticated plan. Each stage is progressively more expensive — fail on the cheapest check first and you save real CI minutes (and money) on every broken PR.

Quick Reference

Tool Needs Credentials? Catches
terraform fmt -check No Formatting/style inconsistencies only
terraform validate No Syntax errors, undeclared references
tflint No Deprecated syntax, provider-specific rule violations
terraform plan Yes Real cloud-side errors — invalid AMIs, IAM issues, quota limits
Error Root Cause Fix
Error: Unsupported argument Argument name doesn't exist on that resource type Check the provider documentation for the exact argument name
Error: Missing required argument A required field was left out of the resource block Add the missing argument — the error names it explicitly
Tflint reports rule violations validate did not catch This is expected — they check different things Treat tflint failures seriously even when validate passed cleanly
Failed to load plugins in tflint Plugin version in .tflint.hcl doesn't match what tflint --init downloaded Re-run tflint --init after updating the plugin version

Frequently Asked Questions

If terraform plan already catches errors, why run validate and tflint separately?

terraform validate only checks syntax and internal type consistency — it doesn't need cloud credentials and runs in milliseconds, making it ideal for a pre-commit hook or the first CI step, well before a slower plan needs provider auth. tflint goes further, catching things validate deliberately ignores: unpinned provider versions, deprecated arguments, invalid instance types for a given cloud, and unused declared variables — issues that are valid HCL but bad practice.

Where do these two tools typically fit in a CI pipeline, and what do they miss?

Run both before `plan`, as cheap fail-fast gates — validate needs no credentials so it can run on every push, even forks and untrusted PRs. Neither tool catches drift, cost issues, or security misconfigurations (e.g. an open security group); pair them with a policy scanner like tfsec or Checkov for that. A common gotcha: validate can pass locally with stale provider plugins cached, then fail in CI after `terraform init -upgrade`.