Tutorials

AWS VPC Tutorial

Key Takeaways

  • An AWS VPC is a logically isolated virtual network—not a private-cloud deployment or a guarantee of security.
  • A public subnet has a route to an internet gateway. Direct IPv4 access also requires public addressing and security rules that permit the connection.
  • Route tables select the most specific matching destination. A default route is explicit configuration, not an automatic internet fallback.
  • Security groups are stateful; network ACLs are stateless. NAT Gateways add ongoing charges, including when your tutorial workloads are idle.

An AWS VPC is a logically isolated network where you control address ranges, subnets, routes, and connectivity to other networks. This AWS VPC tutorial explains those pieces, then creates a public/private subnet layout using CloudFormation instead of relying on changing console screens.

What Is an AWS VPC?

Start with a familiar application: web servers accept internet requests, while a database should accept connections only from the application tier. A VPC gives you the network boundaries and routing controls to build that arrangement; security rules determine which connections those paths actually permit.

If instances are new to you, the Amazon EC2 tutorial covers the compute side of this example.

Public cloud, private cloud, and VPC

A public cloud provides cloud services to multiple customers using provider-managed infrastructure. A private cloud dedicates cloud infrastructure to one organization. An AWS VPC is a virtual network within AWS; creating one doesn't turn your deployment into a private cloud or expose a customer-managed VLAN.

Resources in different subnets of the same VPC don't need a VPN to communicate. They need a valid network path and permission through the applicable security controls.

Advertisement

IP Addresses, Subnet Masks, and CIDR

IPv4 addresses contain 32 bits, conventionally written as four decimal octets. The original example below is useful for understanding the notation, not as a private address range to deploy:

156.67.154.16
10011100.01000011.10011010.00010000

Subnet mask: 255.255.255.0
Binary mask: 11111111.11111111.11111111.00000000

The mask identifies 24 network bits and eight remaining bits. In Classless Inter-Domain Routing (CIDR) notation, that network is 156.67.154.0/24. The address 156.67.154.16 is offset 16 within that network—not a separate host IP address of 0.0.0.16.

CIDR replaces the old class-based allocation model with explicit prefix lengths. A larger prefix number means a smaller address block.

How subnetting divides an address range

For the AWS examples, use 10.0.0.0/16. One possible subnet is 10.0.1.0/24. Borrowing two more bits for its network prefix creates four /26 blocks:

/26 mask: 255.255.255.192
Binary:   11111111.11111111.11111111.11000000

Borrowed bits   Resulting subnet    Address range
00              10.0.1.0/26        10.0.1.0   - 10.0.1.63
01              10.0.1.64/26       10.0.1.64  - 10.0.1.127
10              10.0.1.128/26      10.0.1.128 - 10.0.1.191
11              10.0.1.192/26      10.0.1.192 - 10.0.1.255

Borrowing three bits would create eight smaller blocks. You don't deploy the parent /24 subnet and overlapping child subnets together; choose one allocation scheme.

How many usable addresses does an AWS subnet have?

For standard AWS IPv4 subnets, AWS reserves the first four addresses and the last address. Calculate usable capacity per subnet, not once for the entire VPC:

Usable IPv4 addresses = 2^(32 - prefix length) - 5

/24: 256 total   - 5 = 251 usable
/20: 4,096 total - 5 = 4,091 usable
/26: 64 total    - 5 = 59 usable

Plan non-overlapping ranges for other VPCs, on-premises networks, and future expansion. AWS's VPC planning guide also recommends considering address requirements across accounts and VPCs before allocating ranges.

AWS VPC Subnets and Route Tables

A VPC spans the Availability Zones in its Region, but each subnet belongs entirely to one Availability Zone. Subnets divide address space and provide routing and security boundaries; subnetting doesn't inherently make traffic faster.

Public and private describe routing, not whether every resource has a public or private address. Both subnet types can contain instances with private IPv4 addresses.

Subnet typeRelevant routingTypical use
PublicDirect route to an internet gatewayInternet-facing entry points; public zonal NAT Gateways
Private with outbound accessNo direct internet-gateway route; IPv4 default route to a public NAT GatewayApplication servers needing outbound internet access
IsolatedNo route outside the VPCWorkloads that need only VPC-local connectivity

How an AWS VPC route table works

A route contains a destination and a target. The destination usually identifies an IP range; targets include local, an internet gateway, a NAT Gateway, a peering connection, a transit gateway, or a virtual private gateway.

Longest-prefix match means the most specific matching destination wins. For this tutorial's 10.0.0.0/16 VPC, the two subnet route tables have these routes:

Route tableDestinationTarget
Public10.0.0.0/16local
Public0.0.0.0/0Internet gateway
Private10.0.0.0/16local
Private0.0.0.0/0Public zonal NAT Gateway

A packet addressed to 10.0.2.10 matches both destinations, but /16 beats /0, so it follows the local path. An internet destination instead follows the configured default route.

Destination IP
  |
  +-- One or more matching routes? -- yes --> Most specific match --> Target
  |
  +-- No matching route, including no default? --> No route

There is no automatic internet fallback. Also, a local route supplies a path, not authorization: security groups, network ACLs, and host firewalls can still block communication.

These are AWS-managed routing configurations, not RAM tables you administer on a router. Under AWS's subnet route-table association rules, each subnet uses exactly one route table at a time. Several subnets may share a table; without an explicit association, a subnet uses the VPC's main route table.

Internet Gateway vs NAT Gateway

In AWS, focus on the gateway's specific job rather than a generic router-versus-gateway analogy. An internet gateway attaches to the VPC—not to a subnet—and provides internet connectivity for appropriately addressed resources and routes.

For direct IPv4 internet communication, an EC2 instance needs:

  • A subnet route to an internet gateway attached to its VPC.
  • A public IPv4 address or Elastic IP mapped to its network interface's private IPv4 address.
  • Security-group and network-ACL rules allowing the relevant traffic.
  • A host firewall and listening application that permit the connection, when accepting inbound requests.

The internet gateway doesn't automatically assign public addresses. EC2's public-address assignment settings are separate from routing.

A public zonal NAT Gateway sits in a public subnet and uses an Elastic IP for internet egress through the internet gateway. Private instances initiate connections through it without needing their own public IPv4 addresses; it doesn't accept unsolicited internet connections for those instances.

Public web instance [web security group]
  private IPv4 + mapped public IPv4
    --> public route table --> internet gateway --> internet

Private instance [private-tier security group]
  private IPv4 only
    --> private route table --> zonal NAT Gateway in public subnet
    --> internet gateway --> internet

Web tier --> VPC-local route --> database [database security group]
           No NAT or VPN required; security rules still apply.
AWS VPC diagram showing public instances and a NAT Gateway in Availability Zone A serving private instances in Availability Zone B
Refer to the routing examples above when tracing public and private traffic. For zonal NAT resilience, AWS recommends a NAT Gateway in each Availability Zone with workloads routed to their same-AZ gateway.

Availability Zones and NAT resilience

With zonal NAT Gateways, AWS recommends a gateway in each active AZ and routing private workloads to the gateway in their own AZ. Sharing one across AZs introduces a dependency on that gateway's AZ and can add cross-AZ transfer charges.

VPC 10.0.0.0/16                         Attached internet gateway
                                        ^              ^
AZ A                                    |              |          AZ B
Public 10.0.1.0/24: NAT A ---------------+              +-- NAT B: Public 10.0.3.0/24
  ^                                                            ^
  | Private RT A: default -> NAT A   Private RT B: default -> NAT B |
Private 10.0.2.0/24                              Private 10.0.4.0/24

Both public subnets: public route table, default -> internet gateway

IPv4 vs IPv6 routing

IPv6 uses 128-bit addresses, rather than IPv4's 32 bits. For example, 2001:db8:1234::10 uses hexadecimal groups and compressed zeros.

A dual-stack subnet has both IPv4 and IPv6 CIDRs. Configure IPv6 addresses, routes, and security rules separately: 0.0.0.0/0 doesn't cover IPv6.

For native IPv6 internet connectivity, use ::/0 to an internet gateway. For outbound-initiated IPv6 connections without unsolicited inbound connections, use an egress-only internet gateway. Ordinary IPv6-to-IPv6 connectivity doesn't require IPv4-style NAT.

DNS64 and NAT64 address a different problem: IPv6-only workloads reaching IPv4-only destinations. They don't make an IPv6-only web origin reachable by IPv4-only clients; provide an IPv4-capable frontend for those clients.

AWS VPC Security Groups vs Network ACLs

A VPC supplies isolation and configurable controls, not automatic application security. AWS's security-group and network-ACL comparison highlights their different behavior:

ControlSecurity groupNetwork ACL
Applied toNetwork interfaces associated with the groupTraffic entering or leaving an associated subnet
RulesAllow onlyAllow and deny
Connection trackingStateful: response traffic is automatically allowedStateless: response traffic needs matching rules
EvaluationCombined applicable allow rulesLowest-numbered matching rule first

For the web/database example, create separate groups. Allow the web tier's intended inbound traffic, then allow the database's listener port only from the web tier's security group. A security-group reference can identify that source without granting access to the whole VPC CIDR.

With custom ACLs, account for ephemeral ports—temporary client ports used for responses. Permitting an outbound HTTPS request alone isn't enough if the ACL blocks its returning packets.

Keep IAM permissions least-privileged, patch workloads, authenticate application requests, and use TLS. Network placement doesn't replace those controls; AWS's VPC security practices are a starting point, not the entire security model.

Private Connectivity: VPNs, Peering, and VPC Endpoints

Site-to-Site VPN vs an HTTPS proxy

AWS Site-to-Site VPN uses IPsec tunnels to connect your network to AWS, with routes directing traffic through the connection. It isn't required for ordinary communication between subnets in one VPC.

A proxy forwards connections; a VPN tunnels network traffic; TLS protects application connections. Neither “VPN” nor “HTTPS proxy” is a universal security ranking. Choose the connection scope and trust boundaries you need, and retain end-to-end application protection where appropriate. See how TLS works for the encryption layer.

VPC peering and Transit Gateway

VPC peering connects two VPCs with non-overlapping address ranges. It isn't transitive: peering A with B and B with C doesn't connect A to C through B.

For hub-and-spoke connectivity among multiple VPCs and on-premises networks, consider AWS Transit Gateway. It has its own attachments and routing configuration; adding a hub doesn't eliminate address-planning requirements.

VPC endpoints and private service access

For basic VPC designs, distinguish these endpoint types:

  • Gateway endpoints: Route-table targets for supported services, including Amazon S3 and DynamoDB. They don't use AWS PrivateLink.
  • Interface endpoints: AWS PrivateLink connectivity through endpoint network interfaces, with DNS directing requests to the endpoint.
  • Gateway Load Balancer endpoints: Route traffic to virtual network appliances, such as inspection systems. “Load balancer endpoint” isn't the correct type name.

An S3 gateway endpoint adds service-prefix-list routes to selected route tables, bypassing the NAT or internet gateway for matching traffic. It has no additional endpoint hourly or data-processing charge. Accessing S3 through an internet gateway is a different path, even though that traffic also remains on the AWS network.

Where supported, endpoint policies restrict access through an endpoint. They don't replace IAM permissions or resource policies, such as S3 bucket policies.

Advertisement

What a Default VPC Contains

Default VPCs are regional, not one global network. Unless removed or changed, AWS's default configuration includes 172.31.0.0/16, a /20 default subnet in each AZ, an internet gateway, and these main-table routes:

172.31.0.0/16  -> local
0.0.0.0/0      -> internet gateway

Each standard /20 subnet has 4,091 usable IPv4 addresses before workloads consume them. Default subnets are initially public and assign public IPv4 addresses to launched instances by default, but those settings can be changed. Internet reachability still depends on security rules.

Archived default VPC route-table screenshot from the original tutorial
Historical console screenshot. The authoritative default-VPC local CIDR is 172.31.0.0/16; use the configuration above rather than relying on archived UI details.

AWS VPC Pricing

Creating the VPC itself has no additional charge. As of October 17, 2026, budget separately for the components and traffic paths you choose:

  • NAT Gateway and public IPv4 pricing: NAT hourly availability and per-GB processing charges, plus public IPv4 charges and applicable data transfer. Regional NAT hourly billing applies per configured AZ.
  • Interface endpoint pricing: endpoint-hours per AZ and data processing.
  • Site-to-Site VPN pricing: connection configuration, Region, and applicable transfer charges affect the total; don't assume one universal hourly rate.
  • Other enabled features, including traffic mirroring and network analysis, can add charges.

Same-Region EC2-to-S3 data transfer has no data-transfer charge, but routing it through NAT can still incur NAT processing charges. An internet gateway isn't a blanket exemption from data-transfer charges.

AWS VPC Tutorial: Create Public and Private Subnets

1. Define the network with CloudFormation

Use a sandbox account, Bash, and an installed AWS CLI authenticated with permission to manage CloudFormation stacks and the EC2 networking resources below. Run the shell examples in the same session. This is a single-AZ learning environment, not a production availability design.

Save this complete template as vpc.yaml. It uses CloudFormation's VPC, subnet, route, and NAT Gateway resources.

Description: Public and private IPv4 subnets with a zonal NAT gateway
Parameters:
  AvailabilityZone:
    Type: AWS::EC2::AvailabilityZone::Name
Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
  InternetGateway:
    Type: AWS::EC2::InternetGateway
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Ref AvailabilityZone
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: false
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone: !Ref AvailabilityZone
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: false
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
  PublicAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable
  PrivateAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet
      RouteTableId: !Ref PrivateRouteTable
  PublicDefaultRoute:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  NatEip:
    Type: AWS::EC2::EIP
    DependsOn: AttachGateway
    Properties:
      Domain: vpc
  NatGateway:
    Type: AWS::EC2::NatGateway
    DependsOn: PublicDefaultRoute
    Properties:
      AvailabilityMode: zonal
      ConnectivityType: public
      SubnetId: !Ref PublicSubnet
      AllocationId: !GetAtt NatEip.AllocationId
  PrivateDefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway
Outputs:
  VpcId:
    Value: !Ref Vpc

Both subnets deliberately disable automatic public IPv4 assignment. The public subnet is still public because of its route. If you later test direct internet access from an instance there, explicitly request a public IPv4 address at launch or associate an Elastic IP.

2. Deploy into an available AZ

The commands below use AWS CLI environment settings, select an available standard AZ, and deploy the stack. Change the Region if needed.

set -euo pipefail
export AWS_DEFAULT_REGION=us-east-1
export AWS_PAGER=""
STACK=stackchief-vpc-lab

AZ=$(aws ec2 describe-availability-zones \
  --filters Name=state,Values=available Name=zone-type,Values=availability-zone \
  --query 'AvailabilityZones[0].ZoneName' --output text)

if [[ -z "$AZ" || "$AZ" == "None" ]]; then
  printf '%s\n' 'No available standard AZ found.' >&2
  exit 1
fi

aws cloudformation deploy \
  --template-file vpc.yaml \
  --stack-name "$STACK" \
  --parameter-overrides "AvailabilityZone=$AZ"

3. Inspect what was created

The inventory is one VPC, two same-AZ subnets, two explicitly associated custom route tables, an attached internet gateway, and one public zonal NAT Gateway with an Elastic IP. The private default route targets NAT; the public default route targets the internet gateway.

Creating a VPC also supplies a main route table, default security group, and default network ACL. Neither custom table above becomes the main table. The default ACL initially permits traffic; define workload-specific security groups before deploying the web and database tiers. This template doesn't create those workloads or their groups.

Retrieve the VPC ID from stack outputs, then inspect routes and associations and subnet address-assignment settings:

VPC_ID=$(aws cloudformation describe-stacks \
  --stack-name "$STACK" \
  --query 'Stacks[0].Outputs[0].OutputValue' --output text)

aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'RouteTables[].{Id:RouteTableId,Associations:Associations,Routes:Routes}' \
  --output json

aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'Subnets[].{Id:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone,AutoPublicIPv4:MapPublicIpOnLaunch}' \
  --output table

Expect both custom tables to have explicit subnet associations and active default routes. Both subnets should report AutoPublicIPv4 as false. The separate main table remains local-only; implicitly associated subnets don't appear with a subnet ID in the route-table API response.

4. Validate connectivity, not just resource names

After adding test workloads with the intended security groups, check the actual flows:

  1. Test the public web listener from an external client after assigning public IPv4 addressing.
  2. Test the database listener from the permitted web tier using its private address.
  3. Test outbound HTTPS from a private instance; it should use the NAT path when the security controls permit it.
  4. Confirm that an external client cannot initiate a direct connection to the private database.

When a test fails, inspect the effective subnet route table, address assignment, security-group rules, ACL rules in both directions, DNS resolution, and the host's firewall and listener. A resource named “public” proves nothing about its effective configuration.

VPC Flow Logs provide network-traffic records for diagnosis. Reachability Analyzer can identify configuration components blocking a supported path; it isn't a substitute for testing your application.

5. Clean up billable resources

Remove any workloads you added outside this template first. Then delete the stack and wait for deletion to complete:

aws cloudformation delete-stack --stack-name "$STACK"
aws cloudformation wait stack-delete-complete --stack-name "$STACK"

If deployment or deletion fails, inspect the stack events and verify the NAT Gateway and Elastic IP inventory. Don't assume that stopping instances or closing your terminal cleans up networking charges.

Archived screenshots from the original walkthrough

The following images are retained as historical reference, not current console instructions. The repeatable template, explicit routing inventory, and validation commands above replace the original wizard walkthrough.

View the original console screenshots
Archived VPC console screenshot showing the original launch-wizard button
Original wizard entry point.
Archived VPC wizard screenshot showing public and private subnet configuration choices
Original network configuration choices.
Archived VPC wizard screenshot showing subnet CIDR and NAT addressing settings
Original subnet and addressing settings.
Archived VPC wizard screenshot showing the create-VPC action
Original creation screen.
Archived subnet inventory screenshot showing public and private subnet CIDR blocks
Original public/private subnet inventory.
Archived private-subnet route table showing local and NAT Gateway routes
Original private route-table view.
Archived public-subnet route table showing local and internet-gateway routes
Original public route-table view.

FAQ

What is an AWS VPC?

An AWS VPC is a logically isolated virtual network in an AWS Region. You define its address ranges, subnets, routes, and connectivity, while separate security controls determine which traffic is allowed.

What is the difference between a public and private subnet in AWS?

A public subnet has a direct route to an internet gateway; a private subnet doesn't. Both can contain resources with private IPv4 addresses, and a private subnet may still have outbound connectivity through NAT or other configured paths.

Does a public subnet automatically make an EC2 instance internet-accessible?

No. Direct IPv4 internet access requires an internet-gateway route, a public IPv4 address or Elastic IP, and security rules permitting the traffic. Inbound application access also requires a listening service and compatible host-firewall rules.

How does a private subnet access the internet?

For outbound IPv4 internet connections, a private subnet can route traffic to a public NAT Gateway, which forwards it through an internet gateway. Native IPv6 outbound-only access can instead use an egress-only internet gateway; configure IPv4 and IPv6 routing separately.

How many IPv4 addresses does AWS reserve in each subnet?

Standard AWS IPv4 subnets reserve five addresses: the first four and the last address in the CIDR block. A /24 therefore has 251 usable IPv4 addresses, and a /20 has 4,091.

Further Reading

Advertisement

Written by Sam Erickson

Pen name of a software engineer with 20+ years in the industry, from Fortune 500 companies to startups. Newer articles are researched and drafted with AI tools I direct and fact-checked against their sources. Author of Mastering the Tech Interview. More about me

Advertisement