A circular dependency occurs when two or more AWS security groups reference each other in their ingress/egress rules. Terraform cannot determine creation order, causing the plan to fail. The fix is to separate security group rules into aws_security_group_rule resources.
Terraform builds a dependency graph to determine resource creation order. When security group A references security group B in its rules, and security group B references security group A, a circular dependency is created. AWS cannot create either group because each depends on the other already existing. This cycle is incompatible with Terraform's linear resource creation model.
Remove all ingress/egress rules from your aws_security_group blocks. Create separate aws_security_group_rule resources for each rule instead.
Before (causes cycle):
resource "aws_security_group" "sg_ping" {
name = "sg-ping"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.sg_8080.id]
}
}
resource "aws_security_group" "sg_8080" {
name = "sg-8080"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.sg_ping.id]
}
}After (breaks cycle):
resource "aws_security_group" "sg_ping" {
name = "sg-ping"
vpc_id = aws_vpc.main.id
}
resource "aws_security_group" "sg_8080" {
name = "sg-8080"
vpc_id = aws_vpc.main.id
}Define rules separately after the security groups. This forces Terraform to create both groups first, then apply rules afterward.
resource "aws_security_group_rule" "sg_ping_from_8080" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
security_group_id = aws_security_group.sg_ping.id
source_security_group_id = aws_security_group.sg_8080.id
}
resource "aws_security_group_rule" "sg_8080_from_ping" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_group_id = aws_security_group.sg_8080.id
source_security_group_id = aws_security_group.sg_ping.id
}Execute terraform plan to confirm Terraform can now calculate a valid resource creation order without cycles.
terraform planYou should see all resources scheduled to be created without cycle errors.
Once the plan looks correct, apply the changes:
terraform applyTerraform will first create both security groups, then create the rules to allow traffic between them.
Why aws_security_group_rule works:
aws_security_group_rule resources are applied in a separate phase after aws_security_group resources. This gives Terraform a clear dependency order: (1) Create empty security groups, (2) Create rules that reference those groups.
Detecting cycles in large configs:
Use terraform graph to visualize your dependency tree and spot cycles:
terraform graph | grep -A5 -B5 "security_group"Alternative: depends_on (not recommended):
While you can use depends_on to force ordering, this only works if there's no actual circular reference. True cycles cannot be resolved with depends_on.
Refactoring existing code:
If migrating from inline rules to aws_security_group_rule, Terraform will plan to destroy and recreate security groups. To avoid downtime, use state manipulation or apply in stages.
Error: Error installing helm release: cannot re-use a name that is still in use
How to fix "release name in use" error in Terraform with Helm
Error: Error creating GKE Cluster: BadRequest
BadRequest error creating GKE cluster in Terraform
Error: External program failed to produce valid JSON
External program failed to produce valid JSON
Error: Unsupported argument in child module call
How to fix "Unsupported argument in child module call" in Terraform
Error: network is unreachable
How to fix "network is unreachable" in Terraform