Create a Custom VPC Based on the Default VPC Layout

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/16

Instead 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:         Enabled

This 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/16

That works, but I wanted a more intentional network range:

10.20.0.0/16

This 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/16

So I wanted this new VPC to use a separate range:

10.20.0.0/16

This 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-2

Find 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_ID

Example output:

vpc-0a7867ac42bc411fe

Then 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 table

Example 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/20

All of them had:

MapPublicIpOnLaunch: true

That 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 yaml

The 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: active

This means:

172.31.0.0/16 -> local VPC routing
0.0.0.0/0     -> Internet Gateway

So 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 yaml

Example 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 Gateway

New 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/16

Subnet 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/20

Final 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 launch

Create the CloudFormation Template

Create a file:

nano vpc-10-20-default-like.yaml

Paste 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 PublicRouteTable

This 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 associations

Deploy 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.yaml

Wait for the stack to complete:

aws cloudformation wait stack-create-complete \
  --region ap-east-2 \
  --stack-name project-vpc-10-20

Then show the stack outputs:

aws cloudformation describe-stacks \
  --region ap-east-2 \
  --stack-name project-vpc-10-20 \
  --query "Stacks[0].Outputs" \
  --output table

Example 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/16

The public subnets are:

ap-east-2c:  subnet-044f8daa7ad6b1b7f
ap-east-2b:  subnet-0cb83a7104be22f3e
ap-east-2a:  subnet-047309c6277c227dc

The public route table is:

rtb-035ce41e2da6663ae

To 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 table

Expected 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 = true

To verify the route table:

aws ec2 describe-route-tables \
  --region ap-east-2 \
  --filters "Name=vpc-id,Values=vpc-02e3c5bf677bfdbe2" \
  --query "RouteTables[].Routes[]" \
  --output table

Expected routes:

10.20.0.0/16  -> local
0.0.0.0/0     -> Internet Gateway

Why 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 instance

A 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 Gateway

However, 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 enabled

In 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

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top