Terraform prevents sensitive values from being used directly in for_each loops because they would be exposed as resource instance keys. Learn how to use nonsensitive(), split keys, or switch to count to work around this constraint.
Terraform treats values as sensitive when they come from sensitive input variables, secret management systems, or resource outputs marked as sensitive. Because for_each uses iteration values as unique identifiers for resource instances, these identifiers are always displayed in plans, logs, and state files. To prevent accidentally exposing sensitive data (like API keys, passwords, or database credentials), Terraform explicitly forbids sensitive values in for_each expressions. The error occurs at plan time when Terraform evaluates your configuration and determines that a value you're trying to iterate over contains sensitive information. This is a safety feature, not a limitation you should work around with caution.
If only the keys or non-sensitive parts of your variable need to be used in for_each, wrap them with the nonsensitive() function (available in Terraform v0.15+). This tells Terraform that the iteration value itself is not sensitive, even if it derives from a sensitive source.
variable "api_keys" {
type = map(string)
sensitive = true
default = {
key1 = "secret-value-1"
key2 = "secret-value-2"
}
}
resource "aws_resource" "example" {
for_each = nonsensitive(toset(keys(var.api_keys)))
name = each.value
value = var.api_keys[each.key] # Value remains sensitive
}The key insight is that keys() extracts non-sensitive identifiers, and nonsensitive() confirms to Terraform that these keys are safe to use as for_each iteration values.
Structure your input variables so that keys and non-sensitive metadata are in one variable, while sensitive values are in another. This avoids marking the entire structure as sensitive and lets you iterate freely over the keys.
variable "resource_names" {
type = set(string)
default = ["database", "cache", "queue"]
}
variable "api_keys" {
type = map(string)
sensitive = true
default = {
database = "secret-db-key"
cache = "secret-cache-key"
queue = "secret-queue-key"
}
}
resource "aws_resource" "example" {
for_each = var.resource_names
name = each.value
value = var.api_keys[each.value] # Lookup in sensitive map
}This pattern is often the cleanest because your loop variable is non-sensitive by design.
If your data structure must remain as a single sensitive object, switch from for_each to count. While less flexible, count works with sensitive values.
variable "services" {
type = map(object({
name = string
key = string
}))
sensitive = true
}
locals {
service_list = values(var.services)
}
resource "aws_resource" "example" {
count = length(local.service_list)
name = local.service_list[count.index].name
value = local.service_list[count.index].key
}Note: With count, you reference resources by index (aws_resource.example[0], aws_resource.example[1], etc.) rather than by key, which can be less intuitive.
Use a local value to transform your sensitive data, extracting only the keys you need for iteration. This separates the sensitive data retrieval from the iteration logic.
variable "secret_config" {
type = map(object({ value = string }))
sensitive = true
}
locals {
config_keys = nonsensitive(keys(var.secret_config))
}
resource "aws_resource" "example" {
for_each = toset(local.config_keys)
name = each.value
value = var.secret_config[each.value].value
}This approach makes it explicit where sensitive data enters the configuration and where safe iteration happens.
After applying one of the above solutions, run terraform plan to ensure the configuration is valid and no sensitive values are exposed in the output.
terraform plan -out=tfplanCheck that:
- The plan completes without the "Sensitive value not allowed" error
- Sensitive values appear as "(sensitive)" in the plan output, not as plaintext
- Resource instances are created with the expected naming scheme
- No secrets leak into terraform logs
If you still see issues, verify that your nonsensitive() or keys() function is properly extracting the safe values.
Terraform's handling of sensitive values evolved significantly:
- Terraform 0.14 introduced strict sensitive value tracking and blocked their use in for_each
- Terraform 0.15 added the nonsensitive() function to explicitly unwrap sensitive values
- Terraform 1.6 introduced a regression where sensitive values in conditions combined with for_each started failing in some cases
If you're using Terraform 1.5 or earlier, some solutions may not work. Consider upgrading to Terraform 1.6+ for the best handling of sensitive data. Additionally, be cautious when using nonsensitive() - it removes the safety flag, so only use it on values that truly aren't sensitive.
Error: Error rendering template: template not found
How to fix "template not found" error in Terraform
Error: Error generating private key
How to fix 'Error generating private key' in Terraform
Error creating Kubernetes Service: field is immutable
How to fix "field is immutable" errors in Terraform
Error: Error creating local file: open: permission denied
How to fix "Error creating local file: permission denied" in Terraform
Error: line endings have changed from CRLF to LF
Line endings have changed from CRLF to LF in Terraform