VPC Security Groups Audit in Terraform: Best Practices

Last reviewed: 2026-05-27 · 9 min read

Rost Mironenko
Rost Mironenko·Founder, ArchGuard

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.

security-groups.tf✗ Before
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"]  }}
security-groups.tf✓ After
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 instanceUse ALB → SG reference instead
  • RDS databaseAllow only from application security group
  • Lambda in VPCLambda has no public ingress; SG controls outbound
  • ECS taskUse 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.

security-groups.tf✓ After
# 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.

security-groups.tf✓ After
# 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

high

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.

high

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.

medium

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.

medium

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

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