AWS creates a default VPC in many Regions. It is convenient for quick testing because it already has public subnets, an Internet Gateway, and a route table that allows instances with public IP addresses to access the Internet.
However, the default VPC usually uses the 172.31.0.0/16 CIDR block.
In my case, I wanted to create a cleaner custom VPC in the AWS Asia Pacific (Taipei) Region using:
10.20.0.0/16Instead of manually guessing the layout, I first inspected the existing default VPC and then created a new custom VPC with the same general structure.
This post shows how I created a default-VPC-like custom VPC based on the default VPC layout.
Goal
The goal is to create a new custom VPC with:
Region: ap-east-2
VPC CIDR: 10.20.0.0/16
Subnets: Public subnets in ap-east-2a, ap-east-2b, ap-east-2c
Internet: Internet Gateway
Routing: 0.0.0.0/0 -> Internet Gateway
DNS: EnabledThis new VPC is intended to behave like the default VPC for simple EC2 use cases, but with a custom CIDR block.
Why Not Just Use the Default VPC?
The default VPC in my AWS account used:
172.31.0.0/16That works, but I wanted a more intentional network range:
10.20.0.0/16This is useful when planning multiple AWS accounts, Regions, VPNs, or future network connections.
For example, my existing AWS environment already used:
10.0.0.0/16So I wanted this new VPC to use a separate range:
10.20.0.0/16This avoids overlap and keeps the network layout easier to understand.
Important Note: This Is Not a Direct Clone
AWS does not provide a simple “clone this default VPC and change the CIDR” button.
A VPC CIDR block is not something I can simply copy and modify from an existing VPC.
Instead, the process is:
1. Inspect the existing default VPC.
2. Understand its subnet and route table layout.
3. Create a new custom VPC with a similar layout.
4. Use a new CIDR block.So this post does not literally clone the default VPC. It creates a new custom VPC based on the default VPC layout.
Current Default VPC Layout
First, I checked the default VPC in the ap-east-2 Region.
I used AWS CloudShell.
Set the Region:
export AWS_REGION=ap-east-2Find the default VPC ID:
DEFAULT_VPC_ID=$(aws ec2 describe-vpcs \
--region $AWS_REGION \
--filters "Name=is-default,Values=true" \
--query "Vpcs[0].VpcId" \
--output text)
echo $DEFAULT_VPC_IDExample output:
vpc-0a7867ac42bc411feThen I listed the subnets in the default VPC:
aws ec2 describe-subnets \
--region $AWS_REGION \
--filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" \
--query "Subnets[].{SubnetId:SubnetId,AZ:AvailabilityZone,CidrBlock:CidrBlock,MapPublicIpOnLaunch:MapPublicIpOnLaunch,DefaultForAz:DefaultForAz}" \
--output tableExample output:
-----------------------------------------------------------------------------------------------------
| DescribeSubnets |
+------------+-----------------+---------------+----------------------+-----------------------------+
| AZ | CidrBlock | DefaultForAz | MapPublicIpOnLaunch | SubnetId |
+------------+-----------------+---------------+----------------------+-----------------------------+
| ap-east-2b| 172.31.16.0/20 | True | True | subnet-0535025a024d57efa |
| ap-east-2c| 172.31.0.0/20 | True | True | subnet-0eddb0f0588698f94 |
| ap-east-2a| 172.31.32.0/20 | True | True | subnet-00fd56b2584a52e2f |
+------------+-----------------+---------------+----------------------+-----------------------------+The default VPC had three public subnets:
ap-east-2c: 172.31.0.0/20
ap-east-2b: 172.31.16.0/20
ap-east-2a: 172.31.32.0/20All of them had:
MapPublicIpOnLaunch: trueThat means EC2 instances launched into those subnets can automatically receive a public IPv4 address if the launch configuration allows it.
Check the Default Route Table
Next, I checked the route table:
aws ec2 describe-route-tables \
--region $AWS_REGION \
--filters "Name=vpc-id,Values=$DEFAULT_VPC_ID" \
--output yamlThe important part was:
Routes:
- DestinationCidrBlock: 172.31.0.0/16
GatewayId: local
Origin: CreateRouteTable
State: active
- DestinationCidrBlock: 0.0.0.0/0
GatewayId: igw-0e9a8f5adb8b55c24
Origin: CreateRoute
State: activeThis means:
172.31.0.0/16 -> local VPC routing
0.0.0.0/0 -> Internet GatewaySo this default VPC is a simple public VPC layout.
Check the Internet Gateway
I also checked the Internet Gateway:
aws ec2 describe-internet-gateways \
--region $AWS_REGION \
--filters "Name=attachment.vpc-id,Values=$DEFAULT_VPC_ID" \
--output yamlExample output:
InternetGateways:
- Attachments:
- State: available
VpcId: vpc-0a7867ac42bc411fe
InternetGatewayId: igw-0e9a8f5adb8b55c24
OwnerId: '450647752716'
Tags: []So the default VPC layout is simple:
VPC: 172.31.0.0/16
Public subnet: 172.31.0.0/20 in ap-east-2c
Public subnet: 172.31.16.0/20 in ap-east-2b
Public subnet: 172.31.32.0/20 in ap-east-2a
Route: 0.0.0.0/0 -> Internet GatewayNew VPC Layout
I created a new custom VPC using the same pattern, but with 10.20.0.0/16.
The mapping is:
Default VPC: 172.31.0.0/16
New VPC: 10.20.0.0/16Subnet mapping:
Default VPC subnet: 172.31.0.0/20 -> New subnet: 10.20.0.0/20
Default VPC subnet: 172.31.16.0/20 -> New subnet: 10.20.16.0/20
Default VPC subnet: 172.31.32.0/20 -> New subnet: 10.20.32.0/20Final design:
VPC: 10.20.0.0/16
ap-east-2c: 10.20.0.0/20
ap-east-2b: 10.20.16.0/20
ap-east-2a: 10.20.32.0/20
Route: 0.0.0.0/0 -> Internet Gateway
DNS: Enabled
Public IP: Enabled on subnet launchCreate the CloudFormation Template
Create a file:
nano vpc-10-20-default-like.yamlPaste the following template:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Default-VPC-like custom VPC in ap-east-2 with CIDR 10.20.0.0/16.'
Resources:
ProjectVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.20.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
InstanceTenancy: default
Tags:
- Key: Name
Value: project-vpc-10-20
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: project-igw-10-20
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref ProjectVPC
InternetGatewayId: !Ref InternetGateway
PublicSubnetC:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ProjectVPC
AvailabilityZone: ap-east-2c
CidrBlock: 10.20.0.0/20
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: project-subnet-public-ap-east-2c
PublicSubnetB:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ProjectVPC
AvailabilityZone: ap-east-2b
CidrBlock: 10.20.16.0/20
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: project-subnet-public-ap-east-2b
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ProjectVPC
AvailabilityZone: ap-east-2a
CidrBlock: 10.20.32.0/20
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: project-subnet-public-ap-east-2a
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ProjectVPC
Tags:
- Key: Name
Value: project-rtb-public-10-20
PublicDefaultRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetCRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetC
RouteTableId: !Ref PublicRouteTable
PublicSubnetBRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetB
RouteTableId: !Ref PublicRouteTable
PublicSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTable
Outputs:
VpcId:
Description: VPC ID
Value: !Ref ProjectVPC
VpcCidr:
Description: VPC CIDR
Value: 10.20.0.0/16
PublicSubnetC:
Description: Public subnet in ap-east-2c
Value: !Ref PublicSubnetC
PublicSubnetB:
Description: Public subnet in ap-east-2b
Value: !Ref PublicSubnetB
PublicSubnetA:
Description: Public subnet in ap-east-2a
Value: !Ref PublicSubnetA
PublicRouteTable:
Description: Public route table
Value: !Ref PublicRouteTableThis template creates:
1 custom VPC
1 Internet Gateway
3 public subnets
1 public route table
1 default route to the Internet Gateway
3 subnet route table associationsDeploy the CloudFormation Stack
Deploy the CloudFormation Stack
aws cloudformation create-stack \
--region ap-east-2 \
--stack-name project-vpc-10-20 \
--template-body file://vpc-10-20-default-like.yamlWait for the stack to complete:
aws cloudformation wait stack-create-complete \
--region ap-east-2 \
--stack-name project-vpc-10-20Then show the stack outputs:
aws cloudformation describe-stacks \
--region ap-east-2 \
--stack-name project-vpc-10-20 \
--query "Stacks[0].Outputs" \
--output tableExample output:
---------------------------------------------------------------------------------
| DescribeStacks |
+-----------------------------+--------------------+----------------------------+
| Description | OutputKey | OutputValue |
+-----------------------------+--------------------+----------------------------+
| Public route table | PublicRouteTable | rtb-035ce41e2da6663ae |
| VPC ID | VpcId | vpc-02e3c5bf677bfdbe2 |
| VPC CIDR | VpcCidr | 10.20.0.0/16 |
| Public subnet in ap-east-2c| PublicSubnetC | subnet-044f8daa7ad6b1b7f |
| Public subnet in ap-east-2b| PublicSubnetB | subnet-0cb83a7104be22f3e |
| Public subnet in ap-east-2a| PublicSubnetA | subnet-047309c6277c227dc |
+-----------------------------+--------------------+----------------------------+The new custom VPC was created successfully.
Verify the New VPC
The new VPC is:
VPC ID: vpc-02e3c5bf677bfdbe2
CIDR: 10.20.0.0/16The public subnets are:
ap-east-2c: subnet-044f8daa7ad6b1b7f
ap-east-2b: subnet-0cb83a7104be22f3e
ap-east-2a: subnet-047309c6277c227dcThe public route table is:
rtb-035ce41e2da6663aeTo verify the subnets:
aws ec2 describe-subnets \
--region ap-east-2 \
--filters "Name=vpc-id,Values=vpc-02e3c5bf677bfdbe2" \
--query "Subnets[].{SubnetId:SubnetId,AZ:AvailabilityZone,CidrBlock:CidrBlock,MapPublicIpOnLaunch:MapPublicIpOnLaunch}" \
--output tableExpected result:
ap-east-2c 10.20.0.0/20 MapPublicIpOnLaunch = true
ap-east-2b 10.20.16.0/20 MapPublicIpOnLaunch = true
ap-east-2a 10.20.32.0/20 MapPublicIpOnLaunch = trueTo verify the route table:
aws ec2 describe-route-tables \
--region ap-east-2 \
--filters "Name=vpc-id,Values=vpc-02e3c5bf677bfdbe2" \
--query "RouteTables[].Routes[]" \
--output tableExpected routes:
10.20.0.0/16 -> local
0.0.0.0/0 -> Internet GatewayWhy There Are No Private Subnets
This VPC was created for a simple EC2 use case.
For my current setup, I only need one EC2 instance that can access the Internet and later connect back to my homelab through WireGuard.
So I do not need:
Private subnets
NAT Gateway
Transit Gateway
Site-to-Site VPN gateway
OPNsense
Router instanceA simple public subnet layout is enough.
If I need a more complete production network later, I can create a different VPC layout with public and private subnets.
Cost Notes
Creating this custom VPC itself does not create a meaningful hourly cost.
The following resources do not normally create hourly charges just because they exist:
VPC
Subnets
Route tables
Route table associations
Internet GatewayHowever, other related AWS resources can create charges, such as:
EC2 instances
EBS volumes
Public IPv4 addresses
Elastic IP addresses
NAT Gateway
Load Balancer
Transit Gateway
Site-to-Site VPN
Data transfer out to the Internet
VPC Flow Logs if enabledIn this template, I did not create a NAT Gateway, Load Balancer, Transit Gateway, or Site-to-Site VPN.
So the template only prepares the VPC network.
The main cost will start when I launch EC2 instances or attach other paid resources.
Did this guide save you time?
Support this site