OpenTofu Error: 'Invalid dynamic for_each value' inside a dynamic block
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
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_eachexpression is a single string or number instead of a collection. - The variable is
nullbecause 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(...)ortry(...)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
-
Confirm what type
for_eachis receiving. Intofu console,type(var.ingress_rules)tells you immediately whether it is a string, null, or a real collection. -
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 = []
}
- 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]
}
}
}
- Guard against
nullso an unset variable produces zero blocks instead of an error:
for_each = var.ingress_rules != null ? var.ingress_rules : []
- 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 }
- Re-run plan to confirm the dynamic block expands cleanly:
tofu validate && tofu plan
Prevention
- Always give collection variables an explicit
typeand adefault = []ordefault = {}sonullnever reachesfor_each. - Wrap optional inputs with a null-coalescing expression before they hit a
dynamicblock. - Use the correct iterator name inside
content; do not mix upeach.value(resource-level) with the dynamic block iterator. - Prefer maps or sets over raw lists when you need stable keys across plans.
- Run
tofu validatein CI to catch type mismatches before apply.
Related Errors
Invalid for_each argument— the resource-levelfor_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 oniterator.valuethat 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.
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?
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.