The problem:
terraform plan was showing a destroy + recreate for an AWS security group on every run — even though nothing in the code had changed. Running terraform apply would destroy and recreate the resource unnecessarily.
The fix:
# The issue: Terraform was tracking ingress rules as an inline block
# but AWS was also managing them separately, causing permanent drift.
# Wrong approach — mix of inline and separate rules:
resource "aws_security_group" "api" {
name = "api-sg"
ingress { # ← This inline block fights with aws_security_group_rule resources
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group_rule" "extra_rule" { # ← conflict!
security_group_id = aws_security_group.api.id
...
}
# Fix: use EITHER inline blocks OR separate rules, never both:
resource "aws_security_group" "api" {
name = "api-sg"
lifecycle {
ignore_changes = [ingress, egress] # Ignore if using separate rule resources
}
}# Also check if the actual AWS resource was modified outside Terraform:
terraform refresh # Syncs state with real AWS state
terraform plan # Now shows accurate diffWhy it happens:
When you mix ingress blocks inside aws_security_group with separate aws_security_group_rule resources, Terraform and AWS get into a conflict — each tries to "own" the rules. On every plan, Terraform sees drift because the two approaches keep overwriting each other. Fix by picking one approach and using lifecycle.ignore_changes to prevent the conflict.