Skip to content
CloudWizz

AUDIT FINDINGS

Common Terraform Mistakes We Find in Infrastructure Audits

The Terraform anti-patterns that show up in almost every infrastructure audit we run — state file mismanagement, hardcoded secrets, overly broad IAM, missing lifecycle protection, and config drift — and what to actually do about each one.

Photo of Vaibhav Pendhare By Vaibhav Pendhare · September 1, 2026 · 8 min read

TL;DR

  • The same handful of Terraform mistakes show up in almost every infrastructure audit we run, regardless of company size or maturity.
  • None of them require a rewrite to fix — every one is fixable in place, incrementally, without touching the infrastructure itself.
  • The two most dangerous, in order: state file mismanagement (can corrupt or lock out infrastructure that’s otherwise healthy) and overly broad IAM policies (a blast-radius problem, not a functionality problem).
  • Most of these mistakes aren’t a sign of a bad team — they’re what happens when Terraform starts as “get this working” and nobody circles back to harden it.
Illustration representing an infrastructure code audit and review process
None of these are exotic. That’s exactly why they’re so common.

Why the same mistakes keep showing up

Terraform is easy to get started with and easy to get working — which is exactly why the hardening step gets skipped. A team sets up a state backend, writes some resources, watches terraform apply succeed, and moves on to the next thing. Nobody circles back, because nothing is visibly broken. The problems below aren’t bugs — they’re gaps that don’t cause an incident until the day they do.

The six we find constantly

1. State file mismanagement

The most common finding, by a wide margin. This shows up in three flavors: state stored locally instead of in a remote backend (so it lives on one engineer’s laptop, or gets lost when they leave), a remote backend with no state locking (so two people running apply at the same time can corrupt the state), or a remote backend that’s technically correct but was set up once and never revisited — no versioning enabled on the S3 bucket, no encryption, no access restrictions on who can read it.

Why it matters: Terraform state often contains secrets in plain text — database passwords, API keys, connection strings — because that’s how Terraform tracks what it created. An unprotected state file is often a bigger secrets-exposure risk than the actual .tf files.

The fix: A remote backend (S3 + DynamoDB for locking, or Terraform Cloud/Enterprise) with versioning and encryption enabled, and access restricted to the people and CI systems that actually need it. This is a few hours of work, not a project.

2. Hardcoded secrets in .tf files

Database passwords, API keys, and tokens typed directly into resource blocks instead of pulled from a secrets manager or marked as sensitive variables. Usually added “temporarily” to get something working, then never removed — and now it’s sitting in git history forever, even if someone deletes it from the current file.

The fix: Pull secrets from AWS Secrets Manager, Vault, or your cloud provider’s equivalent at apply time, mark any necessary variables sensitive = true, and — if something was genuinely exposed — rotate it. Deleting the line doesn’t remove it from git history; the credential should be treated as compromised and rotated.

3. Overly broad IAM policies

Action: "*" and Resource: "*" on a policy that was supposed to grant one Lambda function read access to one S3 bucket. This almost always starts as a debugging shortcut — “let’s just get permissions working, then I’ll tighten it” — and the tightening step never happens because everything works and nobody wants to risk breaking it.

Why it matters: This isn’t a functionality problem, it’s a blast-radius problem. A compromised credential or a misconfigured resource with wildcard permissions can affect far more than what it was actually supposed to touch.

The fix: Use IAM Access Analyzer or CloudTrail-based tooling to see what a role has actually used over the past 90 days, then scope the policy down to that. Do it in a lower environment first, watch for anything that breaks, then apply the same scoped policy to production.

4. No lifecycle protection on critical resources

A production database, a KMS key, or an S3 bucket holding customer data with no prevent_destroy lifecycle block and no deletion protection at the cloud-provider level. A refactor that changes a resource’s identifying attributes (sometimes even a poorly understood rename) can cause Terraform to destroy and recreate it — silently, as part of a plan that looked routine.

The fix: Add lifecycle { prevent_destroy = true } to anything that would be genuinely catastrophic to lose, and enable deletion protection at the provider level too (RDS deletion protection, S3 bucket policies denying delete) as a second, independent layer — Terraform-level protection alone isn’t enough if someone can bypass Terraform entirely.

5. Config drift from manual console changes

Someone made a quick change directly in the AWS console to fix an urgent issue, and it never got reconciled back into the Terraform code. Now terraform plan shows changes every single time it runs, because the code doesn’t match reality — and the team has learned to ignore plan output because it’s “always got some noise in it,” which is exactly how a genuinely dangerous change slips through unnoticed in the same plan output.

The fix: Run terraform plan on a clean codebase and reconcile every difference — either update the code to match the manual change (if it was legitimate) or apply the code to revert the drift (if it wasn’t). The real fix is process: manual console changes should be the exception that gets immediately backported to code, not a normal way of working.

6. Unpinned provider and module versions

No version constraints on providers or modules, so terraform init pulls whatever’s newest at the time — which means the exact same code can behave differently depending on when it’s run, and a provider’s breaking change can break your build with zero code changes on your end.

The fix: Pin provider versions with required_providers version constraints, and pin module sources to a specific tag or commit rather than a branch. Upgrade deliberately, on your own schedule, not because a provider happened to ship a new version overnight. This is also where reaching for a well-maintained, versioned module beats hand-rolled resources copy-pasted between environments — a tagged, tested module release is easier to pin, upgrade, and audit than infrastructure code nobody remembers writing.

227+

Open-source Terraform modules

Maintained across AWS, Azure, GCP, DigitalOcean, and Hetzner — exactly the kind of tagged, tested releases this fix is about.

Browse the registry →

Trade-offs and what we’d avoid

  • Don’t fix all six at once under time pressure. State file security and secrets exposure are the two with real, immediate risk — start there. IAM scoping and lifecycle protection are next. Drift reconciliation and version pinning are lower urgency but still worth scheduling.
  • Don’t assume these findings reflect badly on whoever wrote the code. Every pattern here is what happens naturally without a dedicated platform practice reviewing IaC over time — it’s a gap in process, not a skills problem.
  • Don’t tighten IAM policies in production first. Test the scoped-down policy in a lower environment, confirm nothing breaks, then promote it.
  • Don’t let “the plan always has some noise” become normal. That’s the exact condition that lets a real drift-related incident hide in plain sight.

What to do next

What to do next

01

Run terraform plan on every environment right now. If it proposes changes with nobody’s code pending, that’s drift — your first real finding, for free, in five minutes.

02

See Infrastructure Audit for the full two-week review — cost, reliability, delivery, and security findings like the ones above, prioritized by actual risk.

03

Get a second set of eyes on your state and IAM setup specifically. Those two are the highest-risk items on this list — book a 30-minute call if you want a quick sanity check before your next audit finds them for you.

Related reading: Terraform vs Pulumi vs CDK — if you’re evaluating IaC tools rather than hardening an existing Terraform codebase.

FAQ

What's the single most common Terraform mistake you find? +

State file mismanagement — local state files, no locking, or a state file that's technically remote but was never protected properly. It's the most common because it's the easiest thing to get right on day one and the easiest thing to never revisit once the initial setup works. It's also one of the most dangerous, because a corrupted or conflicting state file can take down infrastructure that itself is perfectly healthy.

Is it worth fixing these issues, or should we just rewrite the Terraform from scratch? +

Almost never worth a rewrite. Every mistake in this post is fixable incrementally, in place, without touching the actual infrastructure the code manages — that's one of the underrated benefits of IaC done right. A rewrite introduces far more risk than fixing what's there, and it takes much longer. Triage by severity (secrets and IAM first, structural issues second) and fix in place.

How do we know if we have config drift without a full audit? +

Run `terraform plan` against every environment right now, with no code changes pending, and read the output. If it proposes changes on a codebase nobody touched, that's drift — the real infrastructure no longer matches what the code says it should be. A clean `terraform plan` with zero proposed changes is the baseline you want; if you've never seen that, you likely have drift already.

Does CloudWizz find these issues in every audit, or is this an exaggeration? +

Not an exaggeration — this is a genuine pattern across nearly every Terraform codebase we review that wasn't built by a team with dedicated platform engineering practice. None of these mistakes reflect badly on the team that wrote the code; they're what happens naturally when Terraform starts as 'get this working' and nobody circles back to harden it once it does.

Have a project that could use a sharper opinion?

Book a 30-min call →