Skip to main content

Security Groups vs Network ACLs

Firewalls & IDS/IPS already covered the general stateless-vs-stateful distinction. Cloud VPCs give you both kinds at once, at two different layers, and knowing which is which - and why you keep both - is a staple interview question.

ConceptAWSGCPAzure
Instance-level, statefulSecurity GroupFirewall rule (VPC firewall)Network Security Group (NSG), instance-scoped
Subnet-level, statelessNetwork ACL (NACL)N/A - GCP has no stateless subnet layerNSG, subnet-scoped
info

GCP folds both layers into one firewall-rules construct attached to the VPC (and optionally scoped by network tag), rather than shipping a separate stateless subnet-level filter. The stateful-vs-stateless concept below still applies to how you should design GCP firewall rules - it's just enforced with one mechanism instead of two.

The instance-level layer: stateful

A security group (or firewall rule attached to an instance/NIC) is stateful. Stateful, as covered in Firewalls & IDS/IPS, means the filter tracks connections: once an outbound (or inbound) packet is allowed to open a connection, the return traffic for that same connection is automatically permitted - you never write a matching rule for the reply.

Practically: if your security group allows inbound TCP 443, a client's SYN gets in, and the server's SYN-ACK and all subsequent response packets sail back out without needing an explicit outbound rule for port 443's ephemeral return traffic. You typically only write rules for the direction that initiates the connection.

The subnet-level layer: stateless

A network ACL attaches to a subnet rather than an instance, and it is stateless: every packet is evaluated against the rule list in isolation, with no memory of prior packets. This means a NACL needs an explicit inbound rule for the request and an explicit outbound rule for the reply - including for the high, ephemeral source ports the client's OS picked, since the NACL has no idea that port belongs to a connection it already approved.

The classic NACL outage

A team opens inbound TCP 443 on a NACL, traffic still doesn't work, and they spend an hour staring at the security group. The missing piece is almost always the outbound ephemeral port range (traditionally 1024-65535, though modern OSes narrow this) - without an outbound rule allowing it, the NACL silently drops every reply packet even though the security group and the inbound NACL rule are both correct.

Why you use both layers together

Security groups and NACLs aren't redundant - they're defense in depth at different scopes:

  • A security group protects one instance and evaluates its own rule set regardless of what else is in the subnet.
  • A NACL protects every instance in the subnet at once, and it's the only layer that can explicitly deny traffic (most security groups are allow-only; unlisted traffic is implicitly denied, but you can't write a security group rule that overrides an allow elsewhere). A NACL deny rule is useful for quickly blocking a known-bad CIDR block network-wide without touching every instance's security group.
  • If an instance's security group is accidentally over-permissive, a correctly scoped NACL is the layer that still contains the blast radius - and vice versa.
Decision

Do the detailed, per-service allow-listing in security groups (they're stateful, so the rules stay short and readable). Use NACLs sparingly, for coarse subnet-wide guardrails - blocking a known-bad range, or enforcing that a database subnet never accepts anything from the public internet even if someone misconfigures a security group later.

Pick this when: You're deciding how much firewalling effort to spend at each layer

Order of evaluation

Traffic reaching an instance in a typical AWS-style design crosses the NACL first (subnet boundary), then the security group (instance boundary) on the way in, and the reverse on the way out. Both must permit the packet; either one denying it is enough to drop the traffic. That layering - a stateless coarse filter guarding the subnet, a stateful fine-grained filter guarding the instance - is the practical reason cloud networks ship two firewall mechanisms instead of one.