This walkthrough covers building a fully functional Virtual Private Cloud (VPC) from scratch, configuring subnets across two Availability Zones, setting up a security group, and launching an EC2 instance running a live web server.
By the end of this lab, the following infrastructure will be running on AWS:
- A custom VPC with a /16 CIDR block
- Four subnets across two Availability Zones (two public, two private)
- An Internet Gateway and a NAT Gateway
- Route tables correctly associated with public and private subnets
- A security group allowing HTTP traffic
- An EC2 instance running Apache, deployed via a User Data script
The Target Architecture
Before touching the AWS console, it helps to understand what the finished infrastructure looks like.

The VPC (lab-vpc: 10.0.0.0/16) spans two Availability Zones: us-east-1a and us-east-1b. Each AZ contains one public subnet and one private subnet. The Internet Gateway sits at the edge of the VPC and handles all inbound and outbound internet traffic for the public subnets. The NAT Gateway, deployed inside the public subnet of AZ-A, gives private subnet resources the ability to make outbound internet requests without being directly reachable from outside.
The route tables on the right of the diagram tell the routing story clearly:
- Public route table: local traffic stays within the VPC (
10.0.0.0/16 → local), everything else goes to the Internet Gateway (0.0.0.0/0 → Internet gateway) - Private route table: local traffic stays within the VPC, everything else goes through the NAT Gateway (
0.0.0.0/0 → NAT gateway)
Web Server 1 will be deployed inside lab-subnet-public2 in AZ-B, wrapped in a security group that permits HTTP traffic on port 80.
Task 1: Create the VPC
Navigate to the VPC console by searching for VPC in the AWS search bar. From the VPC dashboard, choose Create VPC.
Choosing “VPC and more”
AWS presents two options: VPC only (creates just the network shell, requiring manual configuration of all other resources) or VPC and more (the wizard creates subnets, route tables, an internet gateway, and a NAT gateway in a single workflow). Select VPC and more.

Under Name tag auto-generation, keep Auto-generate selected and change the value to lab. This prefix is used to auto-name every resource the wizard creates – lab-vpc, lab-subnet-public1-us-east-1a, lab-rtb-public, and so on.
Set the IPv4 CIDR block to 10.0.0.0/16. This gives the VPC 65,536 private IP addresses to allocate across all subnets.
Configuring subnets and the NAT Gateway
Scroll down to configure the subnet settings:

- Set Number of Availability Zones to 1
- Keep Number of public subnets at 1 and Number of private subnets at 1
- Expand Customize subnets CIDR blocks and set:
- Public subnet CIDR block:
10.0.0.0/24 - Private subnet CIDR block:
10.0.1.0/24
- Public subnet CIDR block:
- Set NAT gateways to In 1 AZ
- Set VPC endpoints to None
- Keep both DNS hostnames and DNS resolution enabled
The NAT Gateway is deployed inside the public subnet so that instances in the private subnet can reach the internet for outbound traffic, software updates, API calls, without being directly reachable from outside.
Reviewing the live preview
As the settings are configured, the preview panel on the right builds the architecture in real time.

By this point, the preview shows three columns populated: subnets, route tables, and network connections, including lab-igw (Internet Gateway) and lab-nat-public1-us-east-1a (NAT Gateway with 1 ENI and 1 Elastic IP). All of this will be created in a single action.
Choose Create VPC.
The creation workflow

AWS runs 18 sequential provisioning steps and logs each one in real time:
- Creates the VPC and enables DNS hostnames and DNS resolution
- Creates two subnets (public and private)
- Creates the Internet Gateway and attaches it to the VPC
- Creates the first route table, adds a route, and associates it with the public subnet
- Allocates an Elastic IP address for the NAT Gateway
- Creates the NAT Gateway and waits for it to become active
- Creates the second route table for the private subnet, adds a route pointing to the NAT Gateway, and associates it
The “Wait for NAT Gateways to activate” step is deliberately blocking. Nothing can proceed until the NAT Gateway is ready, because the private route table needs it to exist before it can reference it.
Note for production environments: The Elastic IP allocation and NAT Gateway are real billable resources. NAT Gateways are one of the more expensive VPC components. Factor this in before leaving one running outside of a lab.
Viewing the completed VPC
Choose View VPC to land on the VPC detail page.

The Details panel confirms:
- State: Available
- IPv4 CIDR: 10.0.0.0/16
- DNS hostnames and DNS resolution: enabled – EC2 instances launched in this VPC will automatically receive human-readable DNS names, which matters when connecting to the web server later
- Default VPC: No – this is a custom VPC, not the AWS-provided default
The Resource map tab shows the live architecture: both subnets in us-east-1a, three route tables (including the VPC’s default main route table), and two network connections – the Internet Gateway and the NAT Gateway.
Task 2: Create Additional Subnets
A single Availability Zone is a single point of failure. To achieve high availability, subnets need to exist in at least two physically separate AZs. This task adds a second public subnet and a second private subnet in us-east-1b.
Creating lab-subnet-public2
In the left navigation pane, choose Subnets, then Create subnet.

Configure the following:
- VPC ID:
lab-vpc - Subnet name:
lab-subnet-public2 - Availability Zone:
us-east-1b - IPv4 CIDR block:
10.0.2.0/24
The IPv4 VPC CIDR block dropdown shows 10.0.0.0/16. This is confirming which VPC the subnet belongs to, not a field to change. The subnet CIDR (10.0.2.0/24) is a /24 block carved from within the parent /16, which is how subnets work. Choose Create subnet.
Creating lab-subnet-private2
Repeat the process for the private subnet:

- VPC ID:
lab-vpc - Subnet name:
lab-subnet-private2 - Availability Zone:
us-east-1b - IPv4 CIDR block:
10.0.3.0/24
Choose Create subnet.
Confirming all four subnets

The subnets list confirms all four are Available and associated with lab-vpc:
| Subnet | AZ | CIDR | Type |
|---|---|---|---|
lab-subnet-public1-us-east-1a |
us-east-1a | 10.0.0.0/24 | Public |
lab-subnet-private1-us-east-1a |
us-east-1a | 10.0.1.0/24 | Private |
lab-subnet-public2 |
us-east-1b | 10.0.2.0/24 | Public |
lab-subnet-private2 |
us-east-1b | 10.0.3.0/24 | Private |
The other subnets visible in the list (with 172.31.x.x CIDRs) belong to the AWS default VPC, these can be ignored.
Associating subnets with route tables
Creating the new subnets is not enough, they need to be associated with the correct route tables. Navigate to Route tables in the left navigation pane.
Private route table: Select lab-rtb-private1-us-east-1a → Subnet associations tab → Edit subnet associations. Ensure both lab-subnet-private1-us-east-1a and lab-subnet-private2 are selected. Save.
Public route table: Select lab-rtb-public → Subnet associations tab → Edit subnet associations. Ensure both lab-subnet-public1-us-east-1a and lab-subnet-public2 are selected. Save.

Both route tables now show 2 subnets in the Explicit subnet associations column. Public subnets route internet-bound traffic to the Internet Gateway; private subnets route it through the NAT Gateway. Both AZs now have correct routing behaviour.
Architecture checkpoint

The VPC skeleton is fully in place. All four subnets are live across two Availability Zones, the Internet Gateway and NAT Gateway are deployed, and the route tables are correctly wired. The only remaining piece is the web server instance inside lab-subnet-public2, that comes after the security group is configured.
Task 3: Create a Security Group
A security group acts as a virtual firewall at the instance level. It controls what traffic is allowed to reach an EC2 instance. Navigate to Security groups in the left navigation pane under Security.
Choose Create security group and configure:
- Security group name:
Web Security Group - Description:
Enable HTTP access - VPC:
lab-vpc
Under Inbound rules, choose Add rule:
- Type: HTTP
- Source: Anywhere-IPv4 (
0.0.0.0/0) - Description:
Permit web requests
Choose Create security group.

The result is a security group with one inbound rule: HTTP / TCP / Port 80 / 0.0.0.0/0. This means any IP address on the internet can send HTTP traffic to any instance this security group is attached to.
A few things worth noting:
- Outbound rules: AWS automatically adds a default outbound rule allowing all traffic out. This is the stateful behaviour of security groups in action. If inbound HTTP traffic is allowed, the instance can send responses back without an explicit outbound rule.
- Scope: The security group is tied to
lab-vpc. It can only be attached to instances inside that VPC. - No SSH rule: Port 22 is intentionally absent. The web server will be configured entirely through a User Data script at launch, so no manual SSH access is needed.
Task 4: Launch the Web Server Instance
Navigate to the EC2 console by searching for EC2 in the AWS search bar. Choose Launch instances.
Configuring the instance
Name: Web Server 1
AMI: Keep the default Amazon Linux 2023 AMI selected.
Instance type: t2.micro
Note: The AWS console may default to t2.micro or t3.micro depending on region availability. Select t2.micro explicitly to match the lab requirements.
Key pair: Select vockey
Network settings – choose Edit and configure:
- Network:
lab-vpc - Subnet:
lab-subnet-public2(public subnet in AZ-B, not the private subnet) - Auto-assign public IP: Enable
- Firewall (security groups): Select existing →
Web Security Group
The User Data script
Expand Advanced details and paste the following into the User data box:
#!/bin/bash# Install Apache Web Server and PHPdnf install -y httpd wget php mariadb105-server# Download Lab fileswget https://aws-tc-largeobjects.s3.us-west-2.amazonaws.com/CUR-TF-100-ACCLFO-2/2-lab2-vpc/s3/lab-app.zipunzip lab-app.zip -d /var/www/html/# Turn on web serverchkconfig httpd onservice httpd start
This script runs automatically with root permissions the first time the instance boots. Breaking it down:
dnf install -y httpd wget php mariadb105-server– installs Apache (httpd), wget for downloading files, PHP, and MariaDB silentlywget ...lab-app.zip– downloads the web application from an S3 bucketunzip lab-app.zip -d /var/www/html/– extracts the application into Apache’s web root, making it immediately accessible on port 80chkconfig httpd on– sets Apache to start automatically on every rebootservice httpd start– starts Apache immediately
This is why no manual SSH or configuration is needed after launch, the infrastructure and application deploy together in a single action.
Choose Launch instance.
Waiting for status checks

After launch, the instance goes through a boot and initialisation sequence. Refresh the instances list periodically until Web Server 1 shows 3/3 checks passed in the Status check column. This typically takes 2–3 minutes.
The high CPU load visible when the web page first loads (72% in this case) is normal. The instance is still completing the User Data script installation in the background.
Verifying the web server
Select Web Server 1 and copy the Public IPv4 DNS value from the Details tab. Open a new browser tab, paste the value, and press Enter.

The web application loads and displays live instance metadata:
- InstanceId: confirms which EC2 instance is serving the request
- Availability Zone: us-east-1b – confirms the server is running in
lab-subnet-public2as intended - Current CPU Load – live data pulled from the instance
What happened end to end: the browser sent an HTTP request on port 80 → the Internet Gateway received it → routed it to lab-subnet-public2 → the Web Security Group checked the inbound rule (TCP/80/0.0.0.0/0 ✓) → the request reached Apache on the EC2 instance → the response travelled back the same path. Every component built in this lab played its role.
Final Architecture

The completed infrastructure matches the target architecture from the start of the lab:
lab-vpcspanning two Availability Zones with a /16 CIDR- Four subnets correctly distributed and routed
- Internet Gateway handling public traffic, NAT Gateway handling private outbound traffic
- Web Server 1 live in
lab-subnet-public2, protected by the Web Security Group
Key Takeaways
CIDR planning matters. The subnet addressing scheme used here (10.0.0.0/24, 10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24) is deliberate, each subnet gets its own /24 block carved from the parent /16. AWS reserves 5 IP addresses in every subnet, so a /24 gives 251 usable addresses.
Route tables define public vs private. A subnet is only “public” because its route table has a route pointing 0.0.0.0/0 to an Internet Gateway. Change that route and it becomes private. The subnet itself has no inherent public or private property.
Security groups are stateful. An inbound allow rule automatically permits the corresponding response traffic out. Network ACLs, by contrast, are stateless and require explicit rules in both directions.
User Data scripts enable automated deployments. Rather than manually configuring a server after launch, the entire application stack was installed and started through a bootstrap script. This is the foundation of infrastructure automation on AWS.
Instance type selection is exact. Lab graders and cost calculators both care about the specific instance type chosen. t2.micro and t3.micro are not interchangeable in a graded context, even though they’re similar in practice. Instance types can be changed after creation by stopping the instance, changing the type, and restarting, but public IP addresses may change as a result unless an Elastic IP is used.
AWS Networking Concepts Explained: VPC, Subnets, CIDR, Route 53, and CloudFront - Ugochukwu Chigbata April 27, 2026
[…] post is also a companion to the lab walkthrough: Build Your VPC and Launch a Web Server on AWS. Read this first, then do the […]