This error means Terraform found a cycle in its dependency graph: two or more resources depend on each other, directly or through references, so there is no valid order in which to create them. Terraform builds resources in dependency order, which requires a graph with a clear beginning and end. A loop has neither, so Terraform stops. The most common real world trigger is two security groups that each reference the other, but any mutual reference can cause it.
Error: Cycle: aws_security_group.app, aws_security_group.db
What this error means
Terraform reads the references between your resources and builds a directed graph, then walks it so that anything referenced is created before the thing that references it. This only works when the graph is acyclic. If resource A references B and B references A, neither can go first, and Terraform cannot linearize the graph. It reports the resources caught in the loop and refuses to plan. The fix is to find the reference that closes the loop and remove or restructure it so the graph flows one way.
Common causes
- Two security groups with inline rules that each reference the other, a classic mutual dependency.
- A resource referencing an attribute of a second resource that in turn references the first.
- An unnecessary
depends_onthat points back into a chain that already depends on the current resource. - Module inputs and outputs wired so two modules depend on each other.
- A resource that references its own output through an intermediate local or data source.
How to fix it
Stabilize
- Read the error. It lists the resources in the cycle, which tells you where to look. In the example the loop is between
aws_security_group.appandaws_security_group.db.
Diagnose
- Render the dependency graph so you can see the edges and find the arrow that closes the loop.
terraform graph > graph.dot
- If you have Graphviz, turn it into an image to make the loop obvious; otherwise search the DOT output for the named resources and trace their edges.
terraform graph | dot -Tsvg > graph.svg
Fix
Break the loop by removing one direction of the reference. For the security group classic, move the inline rules out into standalone aws_security_group_rule resources. The groups can then be created first with no rules, and the rules are added afterward, so the cycle disappears:
resource "aws_security_group" "app" {
name = "app"
vpc_id = var.vpc_id
}
resource "aws_security_group" "db" {
name = "db"
vpc_id = var.vpc_id
}
resource "aws_security_group_rule" "app_to_db" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.db.id
source_security_group_id = aws_security_group.app.id
}
resource "aws_security_group_rule" "db_to_app" {
type = "egress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.app.id
source_security_group_id = aws_security_group.db.id
}
For other cases, remove an unnecessary depends_on, split a resource so one half no longer needs the other, or introduce an indirection such as a variable so the two resources no longer reference each other directly. Then confirm the graph is clean.
terraform plan
How to prevent it
- Prefer standalone rule resources (
aws_security_group_rule,aws_vpc_security_group_ingress_rule) over inline rules when groups reference each other. - Add
depends_ononly when a real ordering exists that references do not already express, and never point it back into your own chain. - Keep module dependencies one directional; if two modules need each other, a shared parent usually belongs between them.
- Run
terraform graphwhen introducing cross resource references so a loop shows up before apply. - Refactor mutual references into an intermediate value or resource rather than pointing two resources at each other.
When to call a senior engineer
If the cycle spans many resources or modules, if breaking it forces resources to be recreated, or if the mutual dependency reflects a real architectural knot rather than a stray reference, untangling it safely takes more than deleting one line. Splitting resources can move state addresses and replace live infrastructure if done carelessly. Erzon engineers can map the graph, break the cycle with the right split, and plan any terraform state mv moves so nothing you depend on gets destroyed.
Related errors we fix
Error: Backend configuration changed Fix this error → Terraform Error: Error acquiring the state lock Fix this error → Terraform Error: Instance cannot be destroyed Fix this error → Terraform Error: Invalid count argument ... the "count" value depends on resource attributes that cannot be determined until apply Fix this error → Want a senior engineer to just fix it?
Paste this exact error into the form. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.
Book a fix