Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for OpenTofu By James Joyner IV · · 8 min read Last reviewed Jul 2026

OpenTofu Error: 'Invalid dynamic for_each value' inside a dynamic block

Quick answer

Fix OpenTofu's 'Invalid dynamic for_each value' error: pass a valid collection to a dynamic block's for_each, handle nulls, and reference iterator.value correctly.

  • #opentofu
  • #terraform
  • #iac
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this OpenTofu error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Exact Error Message


│ Error: Invalid dynamic for_each value

│   on main.tf line 21, in resource "aws_security_group" "web":
│   21:     for_each = var.ingress_rules

│ Cannot use a string value in for_each. An iterable collection is required.

You may also see the null variant:

│ Error: Invalid dynamic for_each value

│ Cannot use a null value in for_each.

What It Means

A dynamic block generates zero or more nested blocks by iterating over its for_each expression. OpenTofu requires that expression to evaluate to an iterable collection: a list, a set, a map, or a tuple. When the value is a string, a number, null, or any non-iterable type, OpenTofu cannot loop over it and stops with Invalid dynamic for_each value.

This differs from a resource-level for_each error: here the problem is specifically the collection you handed to a dynamic block that produces repeated nested blocks (like ingress, setting, or rule).

Common Causes

  • The for_each expression is a single string or number instead of a collection.
  • The variable is null because it has no default and no value was supplied.
  • A variable typed loosely (e.g., any) received a scalar at runtime.
  • Using a list of objects where each element lacks a stable key, when a map or set is expected.
  • Referencing each.value (the resource iterator) inside a dynamic block instead of the dynamic block’s own iterator name.
  • An expression like lookup(...) or try(...) that silently returns a non-iterable fallback.

Diagnostic Commands

Validate to confirm the block and line:

tofu validate

Inspect the actual type and value that for_each will receive:

tofu console
> type(var.ingress_rules)
> var.ingress_rules

Check whether the variable is being set at all:

tofu plan -var-file=prod.tfvars 2>&1 | grep -i "for_each"

Step-by-Step Resolution

  1. Confirm what type for_each is receiving. In tofu console, type(var.ingress_rules) tells you immediately whether it is a string, null, or a real collection.

  2. Type the variable strictly so a scalar is rejected at input time rather than deep inside the block:

variable "ingress_rules" {
  type = list(object({
    port     = number
    protocol = string
    cidr     = string
  }))
  default = []
}
  1. Reference the dynamic block’s own iterator, not the resource iterator. The iterator is named after the block label (ingress) unless you override it:
resource "aws_security_group" "web" {
  name = "web-sg"

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = ingress.value.protocol
      cidr_blocks = [ingress.value.cidr]
    }
  }
}
  1. Guard against null so an unset variable produces zero blocks instead of an error:
for_each = var.ingress_rules != null ? var.ingress_rules : []
  1. If you need reliable addressing, convert a list into a keyed map so each generated block has a stable identity:
for_each = { for r in var.ingress_rules : "${r.protocol}-${r.port}" => r }
  1. Re-run plan to confirm the dynamic block expands cleanly:
tofu validate && tofu plan

Prevention

  • Always give collection variables an explicit type and a default = [] or default = {} so null never reaches for_each.
  • Wrap optional inputs with a null-coalescing expression before they hit a dynamic block.
  • Use the correct iterator name inside content; do not mix up each.value (resource-level) with the dynamic block iterator.
  • Prefer maps or sets over raw lists when you need stable keys across plans.
  • Run tofu validate in CI to catch type mismatches before apply.
  • Invalid for_each argument — the resource-level for_each, distinct from a dynamic block’s.
  • Invalid index — indexing into the produced collection with a key that does not exist.
  • Unsupported attribute — referencing a field on iterator.value that the objects do not have.
  • Invalid value for variable — a variable validation block rejected the input before it reached the block.

Frequently Asked Questions

What types can a dynamic for_each accept? A list, set, map, or tuple. Strings, numbers, booleans, and null are not iterable and trigger this error.

How do I stop a null variable from breaking the block? Use for_each = var.x != null ? var.x : [] or type the variable with default = [] so it is never null.

Why does each.value not work inside my dynamic block? Inside a dynamic "ingress" block the iterator is ingress.value, not each.value. each is only the resource-level iterator.

Should I use a list or a map for for_each? Use a map or set when you need stable, predictable addressing; lists work but a reordered list can cause churn. See the prompt library for dynamic-block refactors and more OpenTofu guides.

Free download · 368-page PDF

Fixed it? Get 500 OpenTofu & DevOps AI prompts — free

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.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

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.