Terraform Error Guide: 'Variables not allowed' — Use Literals or Partial Backend Config
Fix 'Variables not allowed' in Terraform: replace var./local. references in backend and terraform{} blocks with literals or a -backend-config partial config.
- #terraform
- #iac
- #troubleshooting
- #errors
Fixing errors like this? Get 500 free DevOps AI prompts
500 copy-paste AI prompts for the stack you actually run — one PDF, free.
Overview
A handful of places in a Terraform configuration are read during the very early initialization and module-loading phase, long before variables, locals, or resource attributes have values. The terraform settings block, the backend/cloud configuration, module source and version, and required-provider version constraints all fall into this category. When you put a var., local., path., or resource reference in one of them, Terraform rejects it at parse time:
Error: Variables not allowed
on backend.tf line 4, in terraform:
4: bucket = var.state_bucket
Variables may not be used here.
The message is the same regardless of the exact spot — backend block, terraform.required_version, a provider’s version, or a module source. It is not saying your variable is undefined; it is saying that this location categorically cannot contain any expression that depends on evaluation. These settings must be static, constant values so Terraform can act on them before it has a chance to compute anything.
Symptoms
terraform initorterraform validatefails withVariables not allowed/Variables may not be used hereand points at aterraform {},backend,provider, ormoduleline.- The referenced expression contains
var.something,local.something,path.module,data.*, or a resource attribute. - It occurs even though the variable is declared and has a default — the value is irrelevant here; the reference is what is disallowed.
- Common trigger lines:
bucket = var.…/key = var.…inside abackendblock,version = var.…insiderequired_providers,count/versionon aproviderblock, orsource = var.…on amodule. - Replacing the reference with a hard-coded literal makes the error vanish.
Common Root Causes
- Parameterized backend blocks — trying to set
bucket,key,region, ordynamodb_tablefromvar.orlocal.. - Dynamic
terraform.required_versionor providerversion— pulling a version constraint from a variable. - Variable-driven module
source—source = var.module_sourceor building a registry path with interpolation. var./local.in theterraform {}orcloud {}block — organization, workspace name, or hostname sourced from a variable.- Meta-argument confusion — putting
count/for_eachon aproviderblock, or expectingproviderblocks to be dynamic; provider configuration cannot be conditionally generated this way. - Reusing one config for many environments by trying to interpolate the state key or bucket per environment inside the backend block.
Diagnostic Workflow
Reproduce with validate, which parses the configuration without contacting a backend:
terraform init -backend=false
terraform validate
terraform fmt -check # confirms it is a semantic, not formatting, error
The most common offender — a backend block reading variables:
# backend.tf -- INVALID: variables may not be used here
terraform {
backend "s3" {
bucket = var.state_bucket
key = "${var.env}/terraform.tfstate"
region = var.region
}
}
The provider/version variant is just as common:
# INVALID: version constraint cannot come from a variable
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = var.aws_provider_version
}
}
required_version = var.tf_version
}
For the backend case, the supported pattern is partial configuration: leave the dynamic keys out of the block and supply them at init time via -backend-config:
# backend.tf -- VALID partial config: only static, or omitted, values
terraform {
backend "s3" {
region = "us-east-1"
encrypt = true
use_lockfile = true
}
}
# supply the per-environment values at init time
terraform init \
-backend-config="bucket=acme-tfstate" \
-backend-config="key=prod/network/terraform.tfstate"
# or from a file
terraform init -backend-config=backend-prod.hcl
Example Root Cause Analysis
An engineer wanted one root module to serve dev, staging, and prod, so they tried to make the state key follow an env variable:
terraform {
backend "s3" {
bucket = "acme-tfstate"
key = "${var.env}/network/terraform.tfstate"
region = "us-east-1"
}
}
terraform init immediately failed with Variables not allowed ... Variables may not be used here, flagging the key line. The backend is resolved before variables exist, so it can never interpolate var.env. The fix was to move the dynamic part out of the block and into partial config supplied per environment. The static parts stayed inline; the changing key came from a small per-env file:
# backend.tf
terraform {
backend "s3" {
bucket = "acme-tfstate"
region = "us-east-1"
encrypt = true
}
}
# backend-prod.hcl
key = "prod/network/terraform.tfstate"
terraform init -backend-config=backend-prod.hcl
For the sibling problem — a version constraint pulled from var.aws_provider_version — there is no partial-config equivalent; version constraints simply must be literals. The team hard-coded version = "~> 5.0" in required_providers and centralized the value through a .terraform.lock.hcl file and a shared module instead.
Prevention Best Practices
- Treat
terraform {},backend,cloud, and providerversionas constants — never referencevar.,local.,path.,data., or resources inside them. - Parameterize backends with partial config: keep static keys inline and pass environment-specific ones via
-backend-configfiles orKEY=VALUEpairs atinit. - Pin provider and Terraform versions as literals and manage them through
.terraform.lock.hcland shared modules, not variables. - Keep module
sourceliteral; if you need different implementations, usecount/for_eachover multiple module blocks or restructure, rather than interpolating the source. - Use a wrapper (Terragrunt, a Makefile, or a CI script) to inject per-environment
-backend-configconsistently, so no one is tempted to reintroducevar.into the backend. - Run
terraform validatein CI to catch a stray variable reference in a static block before it reaches anyone else.
Quick Command Reference
# Reproduce without touching a real backend
terraform init -backend=false
terraform validate
# Initialize a partial backend with inline key=value overrides
terraform init \
-backend-config="bucket=acme-tfstate" \
-backend-config="key=prod/network/terraform.tfstate"
# Initialize a partial backend from a per-environment file
terraform init -backend-config=backend-prod.hcl
# Re-point an already-initialized directory to new partial config
terraform init -reconfigure -backend-config=backend-staging.hcl
# Format and validate before committing
terraform fmt
terraform validate
Conclusion
Variables not allowed is not about a missing variable — it is Terraform telling you that a setting is read before any evaluation happens and therefore must be a static literal. The classic offenders are backend blocks, required_version, provider version constraints, and module source. For backends, the sanctioned escape hatch is partial configuration supplied through -backend-config at init time; for version constraints and module sources, use literals and manage variation through lock files, shared modules, or a CI wrapper. Keep those early-phase blocks constant and the error disappears for good.
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.