Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Terraform Error Guide: 'Invalid expression' (HCL syntax token and character errors)

Fix Terraform's 'Invalid expression' error: balance braces and quotes, add missing commas, drop stray ${} wrappers, correct heredocs, then run terraform fmt and validate.

  • #terraform
  • #troubleshooting
  • #errors
  • #syntax

Exact Error Message

Invalid expression is a parse-time HCL syntax error. Terraform reached a point where it expected the start of an expression — a value, a reference, a function call — and instead found a token it cannot make sense of. The message pins the file, line, and column.

Error: Invalid expression

  on main.tf line 14, in resource "aws_instance" "web":
  14:   tags = {
  15:     Name = "web"
  16:     Env  "prod"
       ^

Expected the start of an expression, but found an invalid expression token.
Error: Invalid character

  on variables.tf line 7, in variable "region":
   7:   default = us-east-1
                  ^

This character is not used within the language.

What the Error Means

Terraform parses your .tf files into an HCL syntax tree before it evaluates anything. Invalid expression (and its siblings Invalid character and “Expected the start of an expression, but found an invalid expression token”) means the parser hit malformed structure: an unbalanced brace, a missing comma between attributes, an unquoted string, or a stray symbol. Because this is a structural failure, no resource is even loaded — plan and validate both stop at the first broken token.

The caret (^) under the offending line is the most useful part of the message. It points at — or just before — the exact column where the parser gave up.

Common Causes

  • Unbalanced braces, brackets, or quotes. A missing }, ], or " makes the parser run past where the expression should have ended.
  • Missing commas in objects or lists. HCL object attributes on the same line, or list elements, need separators.
  • Unnecessary ${} interpolation wrappers. In Terraform 0.12+ you reference values directly; a bare ${var.x} outside a string is invalid.
  • Mixed or broken heredoc syntax. A heredoc opened with <<EOT but closed with the wrong marker, or indented closing marker without <<-.
  • Reserved keywords or unquoted strings. Bare words like us-east-1 are read as identifiers, not strings.
  • Malformed ternary or operators. A condition ? a b missing the :, or a dangling operator.
  • Stray characters. A smart quote, a stray backtick, or a non-ASCII character pasted from a doc.

How to Reproduce the Error

# main.tf — missing comma between object attributes and a bare value
resource "aws_instance" "web" {
  ami           = "ami-0abcd1234"
  instance_type = "t3.micro"

  tags = {
    Name = "web"
    Env  "prod"          # missing '=' / treated as invalid token
  }

  monitoring = ${var.enable_monitoring}   # stray ${} wrapper outside a string
}
terraform validate
Error: Invalid expression

  on main.tf line 11, in resource "aws_instance" "web":
  11:   monitoring = ${var.enable_monitoring}
                     ^

Expected the start of an expression, but found an invalid expression token.

Diagnostic Commands

Let Terraform find the broken token — validate reports the file, line, and column:

terraform validate

fmt reparses every file and refuses to format ones it cannot parse, often surfacing the exact line:

terraform fmt -check -diff

Check brace and bracket balance quickly:

grep -c '{' main.tf
grep -c '}' main.tf      # counts should match

Catch non-ASCII strays (smart quotes, hidden characters):

grep -nP '[^\x00-\x7F]' main.tf

Step-by-Step Resolution

1. Run terraform fmt first. It reindents and normalizes well-formed code and pinpoints the file it chokes on:

terraform fmt

2. Jump to the line and column from the error. The caret marks where parsing failed; the real mistake is usually on that line or the one above it.

3. Fix the structural issue. Add the missing =, comma, or closing brace; quote bare strings:

tags = {
  Name = "web"
  Env  = "prod"     # added '='
}

4. Remove stray ${} wrappers. In 0.12+ reference values directly; only use ${} inside a string:

monitoring  = var.enable_monitoring
description = "instance in ${var.region}"   # interpolation belongs inside the string

5. Correct heredocs. The opening and closing markers must match exactly; use <<-EOT if you indent the closing marker:

user_data = <<-EOT
  #!/bin/bash
  echo "ready"
EOT

6. Validate again. Re-run terraform validate and repeat — Terraform reports one syntax error at a time, so fixing one may reveal the next:

terraform validate
Success! The configuration is valid.

Prevention and Best Practices

  • Enable an HCL language server (e.g. terraform-ls via the official VS Code extension) so braces, commas, and bad tokens are flagged as you type.
  • Run terraform fmt -check and terraform validate in a pre-commit hook and in CI to block malformed HCL before merge.
  • Reference values directly (var.x, module.y.z); reserve ${} for interpolation inside double-quoted strings.
  • Use <<-EOT heredocs for indented blocks and keep the closing marker on its own line.
  • Avoid pasting config from rich-text sources that inject smart quotes; paste as plain text.
  • For triage, the free incident assistant can read the caret position and point you to the broken token. More patterns in the Terraform guides.
  • Invalid function argument — the HCL parses fine but a function rejects a value; a step past this syntax error.
  • Invalid character — a stray or non-ASCII symbol; the same class of parse failure with a different message.
  • Unbalanced parentheses / Missing newline after argument — companion syntax errors fmt and validate surface the same way.
  • Argument or block definition required — Terraform expected an attribute or block where it found something else, often from the same missing = or comma.

Frequently Asked Questions

Why does ${var.x} cause an Invalid expression error? Terraform 0.12+ uses first-class expressions, so you reference values directly: monitoring = var.x. The ${} interpolation syntax is only valid inside a double-quoted string, e.g. "app-${var.x}". A bare ${} outside a string is no longer valid HCL.

The caret points at a line that looks correct. Where is the real error? The parser reports where it gave up, which is often one token after the actual mistake. Check the line above for a missing comma, closing brace, or quote that let the parser run too far.

How do I find an unbalanced brace in a large file? Compare counts: grep -c '{' file.tf against grep -c '}' file.tf. Mismatched counts confirm a brace problem. An editor with bracket matching (or terraform fmt, which fails on unbalanced braces) will narrow it further.

Does terraform fmt fix syntax errors? No. fmt only reformats code it can already parse. On a file with Invalid expression it will refuse and report the file, but you must fix the structural error by hand. Run fmt after fixing to normalize the result.

Can a heredoc cause this error? Yes. A heredoc opened with <<EOT but closed with a different marker, or an indented closing marker used without the <<- form, leaves the expression unterminated and triggers a parse error. Match the markers exactly and use <<-EOT for indented blocks.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.