Back to publications

Article

Cloud NetworkingJunior MbogningSeptember 17, 20265 min read

Building a production AWS VPC: subnets, routes, NAT, and VPC Endpoints

Network decisions that keep an AWS VPC secure, observable, and scalable as workloads, teams, and interconnections grow.

Building a production AWS VPC: subnets, routes, NAT, and VPC Endpoints

An AWS VPC looks simple while it hosts one application. Choose a CIDR, create a few subnets, add an Internet Gateway and a NAT Gateway, then move on to compute. The limits emerge later: a CIDR overlaps another environment, routes become hard to explain, subnets run out of space, or NAT cost rises without anyone knowing which traffic is responsible.

The network does more than allow workloads to communicate. It must make traffic understandable, limit exposure, and leave room for what comes next: another Availability Zone, Kubernetes, hybrid connectivity, or multiple VPCs.

CIDR is an architectural decision

This VPC uses 10.0.0.0/16. The block provides 65,536 IPv4 addresses in theory. The important question is not whether that is enough space; it is how to use it without creating network debt.

Production AWS VPC address plan
Ranges identify both the tier and Availability Zone.

The values are not special. The convention is. An address in 10.0.21.x should immediately suggest the data tier. That becomes useful in Flow Logs, alerts, and incident analysis.

CIDR overlap is expensive to correct. Two VPCs can use the same range while isolated; the issue appears when they must connect through peering, Transit Gateway, VPN, or Direct Connect. Before allocating 10.0.0.0/16, I check the ranges already used by other environments and the corporate network. At scale, AWS VPC IP Address Manager provides a more formal approach to address governance.

A /24 contains 256 IPv4 addresses, five of which AWS reserves. The remaining 251 addresses are comfortable for a few EC2 instances, but the calculation changes with EKS, ECS, or Lambda in a VPC. Capacity planning must account for network interfaces, not only visible servers.

Route tables define subnet behavior

Calling a subnet private does not define its behavior. Its route table does.

Public
10.0.0.0/16 -> local
0.0.0.0/0   -> Internet Gateway

Private with Internet egress
10.0.0.0/16 -> local
0.0.0.0/0   -> NAT Gateway

Isolated
10.0.0.0/16 -> local

An application subnet can be private while still calling an external API. A data subnet can have no path to the Internet at all. The route table explains that distinction better than the subnet name.

Public, private with egress, and isolated routing
Routes, not names, define how the subnet behaves.

An Internet Gateway enables Internet traffic for resources whose routes allow it. Attaching one to a VPC does not make its resources public.

resource "aws_route" "public_internet" {
  route_table_id         = aws_route_table.public.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.main.id
}

Longest prefix match is the routing rule that matters most. With 10.0.0.0/16 -> local and 0.0.0.0/0 -> NAT, traffic for 10.0.21.40 remains inside the VPC, while traffic for 8.8.8.8 takes the default route. A more specific route such as 10.0.21.0/24 wins over the /16.

I explicitly associate subnets with their intended route tables in Terraform. Relying on the Main Route Table by accident is an avoidable source of surprises.

NAT Gateway is useful, not a default path for every flow

A NAT Gateway lets private workloads reach the outside without accepting inbound Internet connections. In the zonal model, each Availability Zone has its own NAT Gateway, so one zone does not depend on egress hosted in another. This isolation has both a fixed and data-processing cost.

Regional NAT Gateways can simplify multi-AZ egress. They do not address private-connectivity use cases, so the choice should follow the actual traffic path and its availability requirement.

NAT should not become the path for every AWS service call. Gateway VPC Endpoints integrate with route tables for S3 and DynamoDB. Interface Endpoints use PrivateLink and private network interfaces for many other AWS services.

Private AWS connectivity and VPC observability
Endpoints avoid unnecessary NAT egress and observability tools validate intended flows.

The endpoint-versus-NAT decision depends on traffic volume, the number of zones, DNS requirements, security objectives, and total cost. Creating an endpoint for every service without measuring traffic is not an architecture strategy.

DNS, Security Groups, and NACLs solve different problems

DNS is part of the network. RDS uses DNS endpoints, Interface Endpoints rely on private DNS, and hybrid environments commonly need private hosted zones, forwarding rules, and Resolver Endpoints. I keep DNS support and DNS hostnames enabled on the VPC, then document naming conventions and Route 53 zones as the platform grows.

Security Groups are the primary workload control. They are stateful and express relationships such as ALB-SG -> APP-SG -> DB-SG. This is usually clearer than wide CIDR-based rules.

Network ACLs are stateless and operate at subnet level. They are useful when subnet-level control is genuinely required, but an elaborate NACL added by default makes troubleshooting harder. During an incident, the team must be able to identify quickly whether a route, Security Group, NACL, endpoint policy, or DNS setting is blocking traffic.

Observe and test the network before it becomes opaque

Reachability Analyzer answers a focused question: does a theoretical path exist between two components? Private EC2 to RDS on 5432 should be reachable. Internet to private EC2 on 22 should not. It does not replace an application test, but it identifies the network component preventing a path.

VPC Flow Logs show actual traffic. They distinguish an expected ACCEPT from a REJECT caused by a missing rule or an unwanted access attempt. Diagrams describe intent; Flow Logs show what the network is actually doing.

Prepare for the next stage

One VPC is manageable. With several VPCs, the questions change: peering or Transit Gateway? Centralized or distributed egress? Where do firewall inspection, DNS, logs, and endpoints belong? At that point, the network becomes an internal platform with its own standards and lifecycle.

IPv6 deserves the same early consideration. AWS supports dual-stack across many services, but IPv6 egress follows a different model: an Egress-Only Internet Gateway allows outbound IPv6 connections without opening arbitrary inbound access. Planning for it early is easier than bolting it on later.

Conclusion

A good VPC is not the one with the most subnets or networking services. It is the one whose team can explain every IP range, route, and NAT dependency.

Before considering the network complete, I verify required Internet egress, explicitly denied paths, data access on the correct ports, intended endpoint usage, and remaining IP capacity. Terraform describes the desired state; tests and Flow Logs confirm that the network behaves as designed.

Discussion

Comments

Readers can react, ask a question or continue the conversation. A GitHub sign-in is required before posting a comment.