Crisp answer: Security Groups are stateful firewalls attached to instances or ENIs that operate at the resource level. NACLs are stateless firewalls attached to subnets that operate at the subnet boundary. Both must allow traffic for it to pass.
Security Groups:
- Attached to: EC2 instances, Lambda, RDS, EKS nodes, ELBs (via ENI)
- State: STATEFUL — return traffic is automatically allowed
- Rules: allow only, no explicit deny
- Evaluation: ALL rules are evaluated, most permissive wins
- Default: deny all inbound, allow all outbound
# Allow HTTPS inbound from anywhere, PostgreSQL from a specific SG
resource "aws_security_group" "app" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.lambda.id] # reference another SG
}
egress {
from_port = 0
to_port = 0
protocol = "-1" # all traffic
cidr_blocks = ["0.0.0.0/0"]
}
}
NACLs (Network Access Control Lists):
- Attached to: subnets
- State: STATELESS — return traffic must be explicitly allowed
- Rules: allow AND deny (explicit deny is possible)
- Evaluation: rules evaluated in number order, first match wins
- Default: allow all inbound and outbound (default NACL)
Because NACLs are stateless, you must allow ephemeral ports (1024-65535) for return traffic:
Inbound rule: allow TCP port 443 from 0.0.0.0/0
Outbound rule: allow TCP ports 1024-65535 to 0.0.0.0/0 ← return traffic
Which to use:
Security Groups cover 95% of use cases. NACLs add a second layer of defense at the subnet boundary — useful for blocking a specific IP range across an entire subnet, or for compliance requirements that mandate subnet-level controls.
Troubleshooting connectivity:
# Check both layers when debugging:
# 1. Security group inbound rules on the destination
# 2. Security group outbound rules on the source
# 3. NACL inbound on the destination subnet
# 4. NACL outbound on the source subnet (for return traffic)
# Use VPC Flow Logs to see what's being allowed/denied:
aws ec2 describe-flow-logs --filter Name=resource-id,Values=vpc-xxx
What to say in the interview:
"Security Groups are stateful and attached to resources — allow rules only, return traffic is automatic. NACLs are stateless and attached to subnets — they support explicit deny and you must allow return traffic separately via ephemeral ports. I use Security Groups for everything and NACLs only when I need to block a specific IP at the subnet level or satisfy a compliance control. When debugging connectivity I check both layers in order: SG on the destination, SG on the source, NACL on the destination subnet, NACL on the source subnet."