VPC Security Groups Audit in Terraform: Best Practices
Last reviewed: 2026-05-27 · 9 min read
5+ years AWS engineering · Open-source contributor
Last reviewed: 2026-05-27
VPC security groups are the primary network-layer control for AWS workloads. A security group audit in Terraform means identifying three classes of misconfiguration: over-broad ingress rules (particularly 0.0.0.0/0 on non-public resources), missing egress restrictions, and inline rule management that makes changes difficult to review. The AWS Well-Architected Security pillar (SEC05) requires layered network controls — security groups are one layer, not the only one.
Step 1 — Use aws_security_group_rule over inline rules
Inline ingress and egress blocks inside an aws_security_group resource couple all rules to the security group lifecycle. When any rule changes, Terraform may recreate the entire group — which forces dependent resources (EC2 instances, RDS clusters) to restart. Separate aws_security_group_rule resources are independent and can be changed in isolation.
resource "aws_security_group" "app" { name = "app-sg" vpc_id = aws_vpc.main.id ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }}resource "aws_security_group" "app" { name = "app-sg" vpc_id = aws_vpc.main.id revoke_rules_on_delete = true lifecycle { create_before_destroy = true }}resource "aws_security_group_rule" "app_https_ingress" { type = "ingress" security_group_id = aws_security_group.app.id description = "HTTPS from ALB" from_port = 443 to_port = 443 protocol = "tcp" source_security_group_id = aws_security_group.alb.id # SG reference, not CIDR}resource "aws_security_group_rule" "app_db_egress" { type = "egress" security_group_id = aws_security_group.app.id description = "Outbound to RDS" from_port = 5432 to_port = 5432 protocol = "tcp" source_security_group_id = aws_security_group.rds.id}Step 2 — Audit and eliminate 0.0.0.0/0 ingress on non-public resources
The AWS Well-Architected Security pillar infrastructure protection guidance (SEC05-BP01) requires controlling network traffic at all layers. An ingress rule that allows 0.0.0.0/0 on a non-public resource bypasses the VPC perimeter entirely.
When is 0.0.0.0/0 ingress acceptable?
- ✓Application Load Balancer (ALB)— Port 443 only — ALB is the public ingress point
- ✗EC2 instance— Use ALB → SG reference instead
- ✗RDS database— Allow only from application security group
- ✗Lambda in VPC— Lambda has no public ingress; SG controls outbound
- ✗ECS task— Use ALB → SG reference instead
Step 3 — Remove the default allow-all egress rule
AWS adds a default egress rule (0.0.0.0/0, all protocols) to every new security group. This default should be removed and replaced with specific egress rules for the services your workload actually calls. Egress filtering limits the data exfiltration path if a workload is compromised.
# Replace allow-all egress with specific outbound rules# HTTPS to AWS services (Parameter Store, Secrets Manager, ECR)resource "aws_security_group_rule" "app_https_egress" { type = "egress" security_group_id = aws_security_group.app.id description = "HTTPS to AWS service endpoints" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] # AWS endpoints are internet-routed unless using VPC endpoints}# DNS resolutionresource "aws_security_group_rule" "app_dns_egress" { type = "egress" security_group_id = aws_security_group.app.id description = "DNS resolution" from_port = 53 to_port = 53 protocol = "udp" cidr_blocks = [var.vpc_cidr]}VPC endpoints for AWS services (Parameter Store, Secrets Manager, ECR) eliminate the HTTPS internet egress requirement
Step 4 — Use security group references for internal traffic
For traffic between resources within the same VPC, use source_security_group_id instead of CIDR blocks. A CIDR block allows any resource in that subnet; a security group reference allows only resources that are members of the referenced group. This is a significant reduction in blast radius when a subnet is shared across multiple workloads.
# Allow RDS to receive traffic only from the application security groupresource "aws_security_group_rule" "rds_from_app" { type = "ingress" security_group_id = aws_security_group.rds.id description = "Postgres from app tier" from_port = 5432 to_port = 5432 protocol = "tcp" source_security_group_id = aws_security_group.app.id}Common security group misconfigurations in Terraform
Port 22 (SSH) open to 0.0.0.0/0
Remove the rule entirely. Use AWS Systems Manager Session Manager for shell access to EC2 instances — no open ports required.
Port 3389 (RDP) open to 0.0.0.0/0
Remove the rule. Use AWS Systems Manager Fleet Manager for Windows instances. If RDP is required, restrict to a specific IP range or VPN CIDR.
All traffic allowed between all resources in the same SG
Define explicit rules for each traffic flow. A single security group that allows all intra-group traffic defeats micro-segmentation.
allow_all egress default rule not removed
Remove the default egress rule (cidr_blocks = ["0.0.0.0/0"]) and replace with explicit destination-scoped rules.
Related Security pillar articles
IAM Least Privilege in Terraform →
CloudTrail Multi-Region Setup in Terraform →
S3 Encryption Best Practices in Terraform →
AWS Security pillar overview →
Frequently asked questions
What is the difference between inline security group rules and aws_security_group_rule?↓
Inline rules (the ingress {} and egress {} blocks inside aws_security_group) are managed as part of the security group resource. aws_security_group_rule creates each rule as an independent Terraform resource, which means each rule can be added, changed, or removed without affecting others. The Terraform documentation recommends aws_security_group_rule to avoid conflicts when multiple modules manage rules on the same group.
When is 0.0.0.0/0 acceptable in a security group?↓
For a public-facing load balancer on port 443 (HTTPS), 0.0.0.0/0 ingress is expected and correct. For EC2 instances, RDS databases, Lambda functions in a VPC, or any non-public resource, 0.0.0.0/0 is a misconfiguration. The distinction is whether the resource is the public ingress point or a downstream resource.
Does AWS have a default allow-all egress rule on security groups?↓
Yes. AWS adds a default egress rule that allows all outbound traffic (0.0.0.0/0) to any new security group. The AWS Well-Architected Security pillar recommends removing this default rule and replacing it with specific egress rules for the services your workload actually calls.
How do I reference security group IDs instead of CIDR ranges in Terraform?↓
Use source_security_group_id in aws_security_group_rule instead of cidr_blocks. This restricts ingress to resources that are members of the referenced security group, which is more precise than a CIDR range.
Get an automated VPC security group audit
ArchGuard reviews your Terraform for VPC and security group misconfigurations across the AWS Well-Architected Security pillar and delivers a branded PDF in 24 hours.
See how it works