Create your own
Lesson illustration

Designing a Highly Available Two-AZ VPC Architecture

Hello, and welcome to Day 1. This module turns familiar AWS networking components into an architecture you can explain under interview pressure: what is placed in each subnet, how traffic is allowed, why the design survives an Availability Zone failure, and where its trade-offs are.

By the end of this lesson, you should be able to draw and defend a standard two-AZ web-application VPC: public ingress, private application instances, zonal NAT gateways for controlled egress, and private S3 access through a gateway endpoint. This is a common foundation for later discussions of EKS, RDS, CI/CD runners, observability, and production incident diagnosis.


Start with the availability boundary, not the components

An AWS Region contains multiple physically distinct Availability Zones (AZs). A highly available VPC design must avoid assuming that one AZ, one subnet, one NAT gateway, or one EC2 instance will always be available.

For a production-facing application, use two AZs and distribute equivalent infrastructure across both:

AZPublic subnetPrivate application subnet
AZ-AALB node, NAT Gateway AApplication instances / Pods
AZ-BALB node, NAT Gateway BApplication instances / Pods

A practical IPv4 plan might begin with a non-overlapping VPC CIDR such as 10.42.0.0/16:

NetworkExample CIDRRole
VPC10.42.0.0/16Overall address space
Public subnet A10.42.0.0/24Public ALB address, NAT Gateway A
Public subnet B10.42.1.0/24Public ALB address, NAT Gateway B
Private app subnet A10.42.10.0/24Private application capacity in AZ-A
Private app subnet B10.42.11.0/24Private application capacity in AZ-B

The exact CIDRs are less important than the design principles:

  1. Do not overlap with current or likely future networks, including corporate networks, peered VPCs, transit-gateway attachments, or other environments.
  2. Leave room for growth. In Kubernetes-based systems especially, subnet capacity can become a real constraint because nodes, Pods, load balancers, and interfaces consume IP addresses.
  3. Subnets belong to exactly one AZ. You create high availability by deploying comparable subnets and workloads in multiple AZs, not by making one subnet “multi-AZ.”

A subnet is called public when its associated route table has a route to an Internet Gateway. It is not public merely because of its name. Likewise, a private subnet is private because it lacks a direct default route to an Internet Gateway.

A two-AZ VPC: public subnets contain the internet-facing Application Load Balancer and one NAT Gateway per AZ, while private subnets contain Auto Scaling application servers. An S3 gateway endpoint provides a private route from the VPC to Amazon S3.

The traffic model: inbound, outbound, and AWS-service access

The cleanest way to reason about the design is to separate three traffic classes.

1. Internet users reaching the application

A client on the internet reaches a public Application Load Balancer (ALB) over HTTPS. The ALB is deployed into both public subnets, so AWS can route requests to healthy load-balancer capacity in either AZ.

The ALB sends requests to application targets in private subnets. The application instances do not need public IP addresses and should not accept traffic directly from the internet.

This is the intended inbound path:

The Internet Gateway (IGW) is attached to the VPC, not placed inside a subnet. It provides the VPC’s connectivity to the public internet. For traffic to be reachable through it, the relevant subnet route table must point its default route to the IGW, and the resource must have an appropriate public address where applicable.

2. Private application workloads making outbound internet requests

Private workloads still commonly need outbound access: fetching OS packages, contacting an external API, downloading a build dependency, or reaching a public SaaS endpoint.

A NAT Gateway makes this possible without allowing unsolicited internet connections directly to the workload. It must be placed in a public subnet, be assigned an Elastic IP address, and have a route to the IGW. Each private subnet has a default route to its NAT Gateway.

NAT is not an inbound proxy or a load balancer. It translates and forwards connections initiated from inside the VPC; a random internet client cannot initiate a new connection through the NAT gateway to a private instance.

For resilience, private subnet A routes to NAT Gateway A in public subnet A, and private subnet B routes to NAT Gateway B in public subnet B. This zonal alignment matters:

  • If AZ-A fails, workloads in AZ-B retain their own NAT path.
  • You avoid cross-AZ data transfer for normal egress.
  • A failure of NAT Gateway A does not remove internet egress from workloads in AZ-B.

A single NAT gateway is sometimes acceptable in a low-cost development environment. It is a deliberate availability and blast-radius trade-off, not the highly available production default. NAT gateways also incur hourly and data-processing charges, so remove them promptly after any hands-on work.

3. Application workloads accessing Amazon S3

If workloads need S3 for artifacts, reports, uploads, or configuration, sending that traffic through a NAT gateway is unnecessary. Add an S3 Gateway VPC Endpoint and associate it with the private route tables.

AWS adds a route for the AWS-managed S3 prefix list. S3-destined traffic follows that more-specific route rather than the generic 0.0.0.0/0 NAT route.

A gateway endpoint improves the network path and avoids NAT gateway data-processing costs for S3 traffic. It does not bypass authorization: IAM policies, bucket policies, encryption requirements, and S3 Block Public Access still control access to the bucket.

Watch the following short walkthrough to reinforce the mapping between AZs, subnets, routes, NAT, and S3 access.

How to Create an AWS VPC with Public and Private Subnets

Watch “How to Create an AWS VPC with Public and Private Subnets” by Be A Better Dev. It shows the VPC wizard choices and, more importantly, the route-based meaning of public and private subnets.

Watch AZ planning for VPC CIDR selection and why two AZs improve service continuity. Then watch subnet layout to see public and private subnet distribution. Finish with egress paths and route review. Focus on which route table association makes each subnet public or private.


Route tables: the decision point for packet placement

Every route table has a local route for the VPC CIDR. It permits routing among subnets inside the VPC; security controls still determine whether the communication is allowed.

A compact IPv4 route-table design is:

Associated subnet(s)DestinationTargetMeaning
Both public subnets10.42.0.0/16localInternal VPC routing
Both public subnets0.0.0.0/0Internet GatewayInternet-capable subnet
Private app subnet A10.42.0.0/16localInternal VPC routing
Private app subnet AS3 prefix listS3 Gateway EndpointPrivate S3 path
Private app subnet A0.0.0.0/0NAT Gateway AZonal outbound internet path
Private app subnet B10.42.0.0/16localInternal VPC routing
Private app subnet BS3 prefix listS3 Gateway EndpointPrivate S3 path
Private app subnet B0.0.0.0/0NAT Gateway BZonal outbound internet path

The route selection rule is longest prefix match. For an S3 IP address, the S3 prefix-list route is more specific than 0.0.0.0/0, so it is selected. For an external API address, no more-specific route exists, so the default route sends traffic through the NAT gateway.

A useful interview correction: do not attach the same private route table with a default route to NAT-A to both private subnets merely because it is convenient. It may work while AZ-A is healthy, but it creates a dependency on AZ-A and can introduce cross-AZ traffic. Use one private route table per AZ when each has its own NAT gateway.

Read the AWS reference architecture now; it provides the route-table and security-group details worth being able to reproduce on a whiteboard.

Example: VPC with servers in private subnets and NAT

Read AWS’s “Example: VPC with servers in private subnets and NAT.” It is the reference layout for this lesson and ties route tables, NAT gateways, private servers, a load balancer, and an S3 gateway endpoint together.

In the “Overview” and “Routing” material, read from the architecture summary. Then follow the “Routing” section through the private routes. Finally, in the “Security” section, read the server security-group rationale. Compare AWS’s tables with the compact design above, and notice that S3 gets its own prefix-list route rather than using the NAT default route.


Security groups and network ACLs: different layers, different jobs

Routes answer where traffic could go. Security groups and network ACLs answer whether it is allowed.

Security groups: primary workload-level controls

A security group (SG) is stateful: if inbound traffic is permitted and a connection is established, return traffic is automatically allowed. Similarly, allowed outbound connections can receive return traffic without a separate inbound rule.

Use SG-to-SG references instead of broad CIDRs wherever the source is another AWS workload. They express intent more reliably than IP addresses, especially when Auto Scaling changes instance IPs.

A minimum application pattern includes:

Security groupInbound rulesTypical outbound rules
ALB SGTCP 443 from 0.0.0.0/0; optionally TCP 80 only to redirect to HTTPSTCP application port to App SG
App SGTCP application port, such as 8080, from ALB SG onlyHTTPS to approved destinations, plus required DNS, telemetry, package, or dependency access
Database SG if presentDatabase port from App SG onlyUsually minimal; depends on engine and operational needs

Two subtleties matter in an interview:

  • ALB health checks originate from the load balancer. If the application listener and health-check port differ, the App SG must allow both from the ALB SG.
  • Security groups are attached to elastic network interfaces, not to a subnet. The same app SG can protect replacement instances created by an Auto Scaling Group.

Avoid rules such as SSH from 0.0.0.0/0. Prefer AWS Systems Manager Session Manager for administrative access, with IAM controls and auditability. That choice removes the need for a bastion host in many environments, though a tightly controlled bastion can still be appropriate in some organizations.

Network ACLs: subnet-level guardrails

A network ACL (NACL) is associated with a subnet and is stateless. Each inbound and outbound direction is evaluated independently, and rules are processed in numerical order; the first matching rule wins. NACLs can explicitly allow or deny CIDRs.

That stateless nature is the source of common outages. If you allow inbound TCP 443 from a client, the response uses an ephemeral port on the client side. A NACL needs rules that allow both the request and the return flow. The exact ephemeral port range depends on the client operating system, so teams must define and document their intended range carefully.

In many application VPCs, use NACLs as a coarse boundary and security groups as the precise application control:

  • The public-subnet NACL can permit expected web traffic and return traffic.
  • The private-app-subnet NACL can allow required internal application flows and egress return flows.
  • A NACL can provide a subnet-wide explicit deny for a known malicious CIDR, something security groups cannot express as a deny rule.

Do not overengineer NACLs just to demonstrate security. Complex NACLs are difficult to maintain and can block legitimate ephemeral return traffic. The default NACL allows all traffic; a custom NACL should be introduced only when there is a clear organizational or network-boundary requirement.


Defending the design in an interview

For a prompt such as “Design a highly available web application in AWS,” start by stating the assumptions: public HTTPS traffic, compute that needs outbound updates and third-party API access, and S3 artifact or object access. Then present the architecture in a deliberate order.

A concise defense

I would use a non-overlapping VPC CIDR with two public and two private subnets distributed across two Availability Zones. An internet-facing Application Load Balancer spans the public subnets, while application instances run in an Auto Scaling Group across private subnets and have no public IPs. Each private subnet has its own route table and uses a NAT Gateway in the same AZ for outbound internet access. The public subnets route internet traffic through an Internet Gateway.

I would add an S3 gateway endpoint to the private route tables so S3 traffic does not traverse NAT gateways. The ALB security group accepts only required public HTTPS traffic; the application security group permits only the ALB security group on the application and health-check ports. Security groups are the primary stateful control, while network ACLs provide optional subnet-level guardrails. If one AZ fails, the ALB routes to healthy targets in the other AZ, and that remaining AZ retains its own NAT path.

That answer establishes availability, security, routing, and cost awareness without drowning the interviewer in service names.

Expected trade-offs

A strong answer includes the limits of the design.

DecisionBenefitCost or limitation
NAT Gateway per AZBetter AZ isolation and avoids normal cross-AZ egressHourly and data-processing cost per NAT Gateway
Private instances without public IPsSmaller attack surfaceRequires a managed access method and egress design
S3 Gateway EndpointPrivate S3 route and avoids NAT processing for S3Supports S3 traffic only; S3 authorization is still required
ALB across two AZsHighly available L7 ingress and health-based routingNot a replacement for healthy, replicated application targets
Restrictive SG referencesLeast privilege and resilience to changing private IPsRequires clear SG ownership and dependency management
Custom NACLsCoarse subnet boundary and explicit deny capabilityStateless rules raise operational complexity

One important qualifier: a two-AZ network alone does not make the application highly available. The application must have healthy capacity in both zones; the deployment process must preserve that capacity; and stateful dependencies need their own availability strategy. You will address service selection and failure recovery later in the course.


A cost-conscious console rehearsal

Because NAT Gateways are chargeable, do not create the full architecture casually just to see it in the console. Use the VPC console’s VPC and more page as a configuration rehearsal first.

  1. Open VPC, select Create VPC, then select VPC and more.
  2. Choose two Availability Zones, two public subnets, and two private subnets.
  3. Inspect the proposed CIDRs. Confirm public and private subnets are paired by AZ.
  4. Enable one NAT gateway per AZ and the S3 Gateway endpoint only if you intend to create the VPC.
  5. Before selecting Create, verbally identify the expected route table associations: shared public route table to IGW; one private route table per AZ to its local NAT; both private route tables associated with the S3 gateway endpoint.
  6. If you do create it for practice, tag every resource, inspect the generated route tables and endpoint associations, then delete the VPC through the console as soon as the review is complete. Confirm that NAT gateways and their Elastic IPs are removed.

For a CLI-oriented reference, AWS’s “Create a VPC with private subnets and NAT gateways using the AWS CLI” documents both the construction order and the required cleanup order. When you use it in a later practice session, begin with the subnet and internet-connectivity sections, then add NAT gateways, endpoint, and security groups. Its cleanup section should be treated as part of the build, not as an afterthought.


Key takeaways

A defensible two-AZ VPC design has a few non-negotiable ideas:

  • Subnets are AZ-specific; high availability comes from deploying equivalent capacity across AZs.
  • A subnet is public only when its route table has a route to an Internet Gateway.
  • Private application instances receive user traffic only through the ALB and should have no public IP addresses.
  • A NAT Gateway provides outbound IPv4 connectivity for private workloads; placing one in each AZ and routing locally prevents a single-AZ egress dependency.
  • An S3 Gateway VPC Endpoint provides a dedicated private S3 route and avoids NAT processing for S3 traffic, but it does not replace IAM or bucket-policy authorization.
  • Security groups are stateful and should carry most least-privilege policy through SG references. NACLs are stateless subnet-level controls that require explicit return-path consideration.

Next, you will use this architecture as a packet path and diagnose why a workload cannot reach an intended destination by checking DNS, route tables, gateways, security groups, NACLs, and VPC Flow Logs.

Can't find a good explanation? Sign up and we'll make it for you

Sign up