Terraform Backend Reconfiguration Required: How to Fix
Fix the Terraform 'Backend configuration changed' error. Migrate state between backends (local to S3, S3 to S3), resolve lock conflicts
Troubleshooting
Fix the Terraform cycle error when resources have circular dependencies. Covers dependency graphs, security group rules, module restructuring, and debugging.
Two or more resources reference each other, creating a circular dependency Terraform cannot resolve. Break the cycle by extracting inline rules into separate resources, removing unnecessary depends_on, or restructuring your modules.
When running terraform plan or terraform validate, you see:
Error: Cycle: aws_security_group.a, aws_security_group.bError: Cycle: module.network, module.computeError: Cycle: aws_route53_record.www, aws_lb.main, aws_lb_listener.httpsTerraform builds a directed acyclic graph (DAG) of all resources and their dependencies. When it detects a loop — A depends on B, and B depends on A — it cannot determine which to create first.
The most common cause. Two security groups where each allows traffic from the other:
# BAD — circular dependency
resource "aws_security_group" "app" {
ingress {
security_groups = [aws_security_group.db.id] # depends on db
}
}
resource "aws_security_group" "db" {
ingress {
security_groups = [aws_security_group.app.id] # depends on app
}
}Adding depends_on that creates a loop:
resource "aws_instance" "web" {
depends_on = [aws_security_group.web]
}
resource "aws_security_group" "web" {
# References the instance somehow
tags = {
Instance = aws_instance.web.id # depends on web → CYCLE
}
}module "network" {
source = "./modules/network"
instance_id = module.compute.instance_id # depends on compute
}
module "compute" {
source = "./modules/compute"
subnet_id = module.network.subnet_id # depends on network → CYCLE
}A chain of outputs and variables across modules that forms a circle when all dependencies are resolved.
Instead of inline ingress/egress blocks, use standalone aws_security_group_rule resources:
# Create security groups without inline rules
resource "aws_security_group" "app" {
name = "app-sg"
vpc_id = var.vpc_id
}
resource "aws_security_group" "db" {
name = "db-sg"
vpc_id = var.vpc_id
}
# Add rules as separate resources — no cycle
resource "aws_security_group_rule" "app_to_db" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
source_security_group_id = aws_security_group.app.id
security_group_id = aws_security_group.db.id
}
resource "aws_security_group_rule" "db_to_app" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
source_security_group_id = aws_security_group.db.id
security_group_id = aws_security_group.app.id
}Find exactly where the cycle is:
# Generate the graph
terraform graph | dot -Tpng > graph.png
# Or use text output
terraform graph 2>&1 | grep -E "->|cycle"
# Filter for specific resources
terraform graph | grep -E "(security_group|instance)" | dot -Tpng > filtered.pngIf you don't have dot (Graphviz):
# Install Graphviz
sudo apt-get install graphviz # Debian/Ubuntu
brew install graphviz # macOS
# Or just read the text output
terraform graph 2>&1
# Look for bidirectional arrows between resourcesTerraform automatically infers dependencies from attribute references. Explicit depends_on is rarely needed:
# BAD — depends_on is redundant here (subnet_id already creates dependency)
resource "aws_instance" "web" {
subnet_id = aws_subnet.private.id
depends_on = [aws_subnet.private] # Remove this
}
# GOOD — implicit dependency through attribute reference
resource "aws_instance" "web" {
subnet_id = aws_subnet.private.id # Terraform knows this depends on the subnet
}Only use depends_on when there's a hidden dependency Terraform can't detect (e.g., IAM policy propagation delay).
Break the cycle by passing values through a third module or restructuring:
# BAD — modules reference each other
module "network" {
source = "./modules/network"
instance_id = module.compute.instance_id
}
module "compute" {
source = "./modules/compute"
subnet_id = module.network.subnet_id
}
# GOOD — network creates first, compute depends on it, tags update separately
module "network" {
source = "./modules/network"
}
module "compute" {
source = "./modules/compute"
subnet_id = module.network.subnet_id
}
# Tag the network with instance info as a separate resource
resource "aws_ec2_tag" "subnet_instance" {
resource_id = module.network.subnet_id
key = "Instance"
value = module.compute.instance_id
}If one side doesn't need to be a hard dependency:
# Instead of referencing the resource directly
# Use a data source for the existing resource
data "aws_security_group" "db" {
filter {
name = "group-name"
values = ["db-sg"]
}
depends_on = [aws_security_group.db]
}| Pattern | Cause | Fix |
|---|---|---|
| SG ↔ SG | Inline ingress/egress referencing each other | Use aws_security_group_rule |
| Module ↔ Module | Cross-module attribute references | Restructure or use a shared variable |
Resource ↔ Resource via depends_on | Unnecessary explicit dependency | Remove depends_on, use implicit refs |
| IAM Role ↔ Policy ↔ Instance Profile | Role attached to profile that references role | Separate policy attachment |
| Route53 ↔ ALB ↔ Listener ↔ Certificate | DNS validation for cert needs record, cert needs listener | Use aws_acm_certificate_validation |
terraform graph to visualize the cycledepends_on blocks?aws_security_group_rule for cross-group referencesdepends_on — Terraform infers most dependencies automaticallyterraform validate often — catches cycles before planterraform graph during design — visualize dependencies before they become problemsCycle errors mean Terraform found a circular dependency in your resource graph. The fix is always to break the circle: extract inline rules into standalone resources, remove unnecessary depends_on, or restructure your modules so dependencies flow in one direction. Use terraform graph to see exactly where the cycle is.
Fix the Terraform 'Backend configuration changed' error. Migrate state between backends (local to S3, S3 to S3), resolve lock conflicts
Fix Terraform provider version conflicts between modules, lock files, and constraint mismatches. Resolve dependency lock errors, upgrade providers safely
Fix the Terraform 'Reference to undeclared resource' error. Causes include typos, missing resources, wrong module references, and for_each/count confusion.
Fix the Terraform 'An argument named X is not expected here' error. Common causes include wrong provider version, deprecated arguments, typos