Overview
A virtual private cloud (VPC) is a logically isolated network within a cloud provider, and everything about how traffic flows in, out, and around it is governed by explicit configuration: subnets divide the VPC's address space, route tables decide where traffic destined outside a subnet goes, and security groups/network ACLs filter what's allowed at the resource and subnet level. The recurring point of confusion is that "public" and "private" aren't inherent properties of a subnet; they're just descriptions of what its route table currently does, which means auditing actual routes (not subnet names) is the only reliable way to know what's actually internet-reachable.
Quick Reference
| Concept | Purpose | Scope |
|---|---|---|
| VPC | Logically isolated virtual network | Account/region-wide container |
| Subnet | Address-space subdivision of a VPC | Availability zone |
| Route table | Determines where traffic destined outside a subnet goes | Per subnet |
| Security group | Stateful, per-resource allow-list firewall | Attached resources |
| Network ACL | Stateless, subnet-wide allow/deny rules | Per subnet |
Syntax
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
}
resource "aws_route" "private_nat" {
route_table_id = aws_route_table.private.id
destination_cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}Examples
# Security group - stateful, per-resource. Allowing inbound 443
# automatically permits the matching outbound response traffic.
resource "aws_security_group_rule" "https_in" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.web.id
}Trust route tables, not subnet names
A subnet named "private-subnet" with a route to an internet gateway is still public, regardless of its name. Always verify actual route table entries when auditing what's internet-reachable, naming conventions are not enforcement.
Visual Diagram
Common Mistakes
- Trusting a subnet's name ("private-subnet-1") instead of checking its actual route table; the two can silently disagree after a configuration change.
- Placing a resource that only needs outbound internet access directly in a public subnet with a public IP, exposing it to inbound traffic it never needed to accept.
- Over-relying on security groups alone and never using network ACLs as a defense-in-depth layer, or conversely, fighting stateless NACL rules without understanding why return traffic needs its own explicit allow.
- Forgetting that a security group allowing
0.0.0.0/0inbound on a sensitive port is a standing exposure, not a temporary convenience, if left in place after testing.
Performance
- NAT gateways have their own throughput limits and per-GB data processing cost, very high-outbound-traffic workloads behind a single NAT gateway can hit real bottlenecks and cost, which is why multi-AZ NAT gateway placement matters at scale.
- VPC peering and transit gateway routing add hops between networks, designing the topology (hub-and-spoke vs. full mesh) affects both latency and the blast radius of a routing misconfiguration.
- Security group rule evaluation is effectively free at request time; it's a configuration correctness concern, not a runtime performance one.
Best Practices
- Audit actual route tables, not subnet naming conventions, when determining what's genuinely internet-reachable.
- Put anything that doesn't need to accept inbound internet traffic in a private subnet behind a NAT gateway for outbound access only.
- Use security groups as the primary per-resource control and network ACLs as a coarser, subnet-wide defense-in-depth layer, not the other way around.
- Design VPC topology (peering, transit gateway) deliberately for the traffic patterns you actually have, rather than defaulting to a full mesh that's harder to audit as it grows.