Terraform

Validating Infrastructure as Code with Linting Tools: Improve Quality and Consistency

AGAnurag Gupta04 Apr 2025 Β· Updated 04 Oct 2026 Β· 7 min read
Validating Infrastructure as Code with Linting Tools: Improve Quality and Consistency

Quick answer: Linting Terraform code means running automated checks that catch syntax errors, deprecated arguments, style inconsistencies and insecure configurations before you ever run terraform apply. The four tools most teams rely on are terraform fmt and terraform validate (built in), TFLint for provider-aware rules, and Checkov, Terrascan or Trivy for security and compliance scanning.

If you have ever opened a pull request, waited twenty minutes for a pipeline, and then discovered that a typo in a resource name or an open security group was the cause, this guide is for you. You will learn what each linting layer does, how to install and run the tools, how to wire them into Git hooks and CI, and the mistakes that make teams give up on linting too early.

Why lint Infrastructure as Code at all?

Application developers have had linters for decades: ESLint for JavaScript, pylint for Python, Checkstyle for Java. Infrastructure code deserves the same treatment, and arguably needs it more. A bug in a web page breaks a button; a bug in a Terraform file can expose a database to the internet or delete a production volume.

Linting gives you three things that code review alone cannot:

  • Speed – feedback in seconds on your laptop, not after a long plan in CI.
  • Consistency – every engineer’s code looks the same, so reviews focus on design rather than indentation.
  • Safety – policy rules encode knowledge like “S3 buckets must block public access” so nobody has to remember it.

If you are still learning the language itself, read How to Write Terraform Code: A Beginner’s Guide first, then come back here to make that code production-grade.

The four layers of Terraform validation

It helps to think of linting as layers, each catching a different class of problem.

Layer Tool What it catches Needs cloud credentials?
Formatting terraform fmt Indentation, alignment, spacing No
Syntax and types terraform validate Unknown arguments, wrong types, missing required fields, broken references No (after init)
Best practices TFLint Invalid instance types, deprecated syntax, unused variables, naming conventions No
Security and compliance Checkov, Trivy (formerly tfsec), Terrascan Public buckets, unencrypted disks, wide-open security groups, missing tags No

The first two layers come free with the Terraform binary. The other two are separate open-source tools. Note that tfsec has been merged into Trivy by Aqua Security; existing tfsec users are encouraged to migrate, and new projects should start with Trivy directly.

Hands-on: running the linters

Let us start with a deliberately flawed configuration so you can see each tool do its job.

# main.tf
terraform {
  required_version = ">= 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}

variable "unused_var" {
  type = string
  default = "nobody reads me"
}

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.nano.large"    # invalid instance type
  tags = { Name = "tkh-web" }
}

resource "aws_security_group" "web" {
  name = "tkh-web-sg"
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]   # SSH open to the world
  }
}

Now run the built-in checks. fmt -check exits non-zero if any file needs reformatting, which is exactly what you want in CI; validate needs an init first so the provider schema is available, but -backend=false means no remote state is touched.

terraform fmt -check -recursive
terraform init -backend=false
terraform validate

Both commands pass on the file above. The instance type is a valid string, so validate cannot know it does not exist, and nothing in core Terraform cares about open ports. That is where TFLint and Checkov come in.

# Install (macOS/Linux)
curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash
pip install checkov

# Tell TFLint to load the AWS ruleset
cat > .tflint.hcl <<'EOF'
plugin "aws" {
  enabled = true
  version = "0.31.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}
rule "terraform_unused_declarations" { enabled = true }
EOF

tflint --init
tflint --recursive
checkov -d . --framework terraform

TFLint reports aws_instance_invalid_type for t2.nano.large and terraform_unused_declarations for the variable. Checkov flags CKV_AWS_24 (security group allows ingress from 0.0.0.0/0 to port 22) along with checks for missing EBS encryption and IMDSv2. Each finding includes a check ID, the file and line, and a link explaining the fix β€” far more useful than a reviewer’s “looks wrong?” comment.

Automating linting with pre-commit and CI

Linters only help if they run every time. The pre-commit framework runs them on every git commit so bad code never leaves your laptop.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.96.1
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_tflint
        args: [--args=--config=__GIT_WORKING_DIR__/.tflint.hcl]
      - id: terraform_checkov
        args: [--args=--quiet, --args=--framework=terraform]

Install it with pip install pre-commit && pre-commit install. The same hooks should run again in CI (GitHub Actions, GitLab CI, Jenkins) because a teammate can always skip local hooks with --no-verify. Make the pipeline fail on any finding above a severity you choose; Checkov supports --soft-fail and --check/--skip-check flags so you can roll out strict rules gradually.

Choosing between Checkov, Trivy and Terrascan

All three scan for security misconfigurations and they overlap heavily, so pick one and commit to it rather than running all of them.

  • Checkov (Python, by Prisma Cloud) – the largest policy library, scans Terraform plans as well as HCL, also covers Kubernetes, Dockerfiles and CloudFormation. Custom policies can be written in Python or YAML.
  • Trivy (Go, by Aqua Security) – a single binary that scans container images, file systems and IaC. If your team already uses Trivy for Docker images, adding Terraform scanning is one flag away. Custom rules use Rego (Open Policy Agent).
  • Terrascan (Go, by Tenable) – strong compliance mappings (CIS, PCI-DSS, HIPAA) and Rego-based policies; good when auditors want reports tied to a standard.

A sensible default in 2026 is TFLint plus either Checkov or Trivy. Add Terrascan only if a compliance framework specifically demands it.

Six linting mistakes that waste everyone’s time

  1. Turning on every rule on day one. A legacy repo can produce 800 findings. Start with fmt, validate and high-severity security checks, then tighten rules sprint by sprint.
  2. Suppressing findings without a reason. Inline skips like # checkov:skip=CKV_AWS_24: bastion host, restricted by NACL are fine. Bare skips with no explanation are a future incident.
  3. Running TFLint without the provider plugin. Out of the box TFLint only knows core Terraform rules. The AWS, Azure and Google rulesets are what catch invalid instance sizes and regions.
  4. Forgetting -recursive. Both terraform fmt and tflint only look at the current directory unless told otherwise, so modules in subfolders are silently skipped.
  5. Linting only in CI. A ten-second local hook saves a ten-minute pipeline round trip. Use both.
  6. Treating linting as a replacement for review. Linters check syntax and policy; they cannot tell you the architecture is wrong. Pair them with the dependency and ordering knowledge from Understanding Resource Dependencies and Ordering in Terraform.

Frequently asked questions

Does terraform validate need cloud credentials?

No. It checks your configuration against the provider schema, which is downloaded during terraform init. Use init -backend=false so you do not need access to the remote state either.

Is TFLint the same as terraform validate?

No. validate checks that the configuration is structurally correct. TFLint adds opinionated rules β€” invalid AMI formats, deprecated syntax, unused declarations, naming conventions β€” that Terraform itself does not enforce.

Should I scan the plan output instead of the HCL files?

Ideally both. Scanning HCL is fast and needs no credentials. Scanning terraform show -json plan.out with Checkov resolves variables and module inputs, so it catches issues that only appear with real values.

Can I write organisation-specific rules?

Yes. TFLint supports custom rulesets in Go, Checkov accepts YAML or Python policies, and Trivy and Terrascan use Rego. Common custom rules enforce mandatory tags, approved regions and naming prefixes.

Key takeaways

  • Linting catches formatting, syntax, best-practice and security issues in seconds, long before apply.
  • Use the built-in fmt and validate first, then add TFLint with a provider ruleset.
  • Pick one security scanner β€” Checkov or Trivy β€” and roll out rules gradually with documented skips.
  • Run everything locally via pre-commit and again in CI so nothing slips through.

Want to build pipelines where every Terraform change is linted, tested and deployed automatically? Our DevOps course covers Terraform, CI/CD and cloud security with hands-on projects and placement support. For video walkthroughs of these tools, subscribe to our YouTube channel.

AG
Written byAnurag Gupta

Part of the Techknowledgehub team of industry mentors, writing practical guides to help you build a job-ready tech career.

More articles by Anurag Gupta β†’
Keep reading

Related articles

Leave a Reply