Free tech learning for everyone, from basics to pro

Cloud LabsLabsIntermediate

Lab: AWS VPC with public and private subnets and an EC2 instance

In short: A VPC is your own private network in AWS. You build one with a public subnet that can reach the internet and a private subnet that cannot, launch an instance in each, prove the difference with routes and SSH, and then delete everything in the right order.

Aim and what you need

The aim is to understand what really makes a subnet "public": a route to an internet gateway (IGW) plus a public IP address. A VPC (virtual private cloud) is an isolated network you define with a CIDR block. EC2 (Elastic Compute Cloud) instances are the virtual machines that sit in it.

  • An AWS account with root MFA, an admin IAM user and a budget (see the IAM and MFA lab).
  • The AWS CLI v2 and an SSH client. Your public IP address, written as <your-ip>/32.
  • About 60 minutes. Small instances may be covered by your free offer; public IPv4 addresses are charged hourly. This lab deliberately avoids a NAT gateway, which is billed by the hour.
Lab VPC 10.0.0.0/16VPCeu-west-2, one AZInternet gatewayroute 0.0.0.0/0Public subnet10.0.1.0/24Private subnet10.0.2.0/24Web instancepublic IP, SGApp instanceno public IPSecurity groupsSSH from your IP
Figure 1. Only the public subnet has a route to the internet gateway; the private instance is reached through the public one.

Steps

Build orderVPC10.0.0.0/16Subnetspublic,privateGatewayattach IGWRoutespublic tableInstancesSG and key
Figure 2. Build from the outside in, and delete in the reverse order.
  1. Create the VPC and two subnets in the same availability zone, and save the IDs in shell variables.
  2. Create and attach an internet gateway, then a route table for the public subnet with a default route to it.
  3. Turn on automatic public IPs for the public subnet only.
  4. Create two security groups: one for the web host allowing SSH only from your IP, one for the app host allowing SSH only from the web host's group.
  5. Create a key pair and launch one instance in each subnet using the latest Amazon Linux 2023 image.
export AWS_REGION=eu-west-2
VPC=$(aws ec2 create-vpc --cidr-block 10.0.0.0/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=lab-vpc},{Key=project,Value=lab}]' --query Vpc.VpcId --output text)
PUB=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.0.1.0/24 --availability-zone eu-west-2a --query Subnet.SubnetId --output text)
PRIV=$(aws ec2 create-subnet --vpc-id $VPC --cidr-block 10.0.2.0/24 --availability-zone eu-west-2a --query Subnet.SubnetId --output text)
IGW=$(aws ec2 create-internet-gateway --query InternetGateway.InternetGatewayId --output text)
aws ec2 attach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
RT=$(aws ec2 create-route-table --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 create-route --route-table-id $RT --destination-cidr-block 0.0.0.0/0 --gateway-id $IGW
aws ec2 associate-route-table --route-table-id $RT --subnet-id $PUB
aws ec2 modify-subnet-attribute --subnet-id $PUB --map-public-ip-on-launch
SGW=$(aws ec2 create-security-group --group-name lab-web-sg --description "SSH from my IP" --vpc-id $VPC --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SGW --protocol tcp --port 22 --cidr <your-ip>/32
SGA=$(aws ec2 create-security-group --group-name lab-app-sg --description "SSH from web tier" --vpc-id $VPC --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id $SGA --protocol tcp --port 22 --source-group $SGW
aws ec2 create-key-pair --key-name lab-key --key-type ed25519 --query KeyMaterial --output text > lab-key.pem
chmod 400 lab-key.pem
AMI=$(aws ssm get-parameter --name /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 --query Parameter.Value --output text)
aws ec2 run-instances --image-id $AMI --instance-type t3.micro --subnet-id $PUB --security-group-ids $SGW --key-name lab-key --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=lab-web},{Key=project,Value=lab}]'
aws ec2 run-instances --image-id $AMI --instance-type t3.micro --subnet-id $PRIV --security-group-ids $SGA --key-name lab-key --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=lab-app},{Key=project,Value=lab}]'

How to verify

List the instances: the web instance should have a public IP and the app instance only a private 10.0.2.x address. SSH to the web instance with ssh -i lab-key.pem ec2-user@<web-public-ip>, then use it as a jump host: ssh -i lab-key.pem -J ec2-user@<web-public-ip> ec2-user@<app-private-ip>. From the app host, curl -I https://example.com should fail, which proves the private subnet has no internet route. Compare the two route tables with aws ec2 describe-route-tables --filters Name=vpc-id,Values=$VPC: only the public one has 0.0.0.0/0 to the IGW. An SSH attempt from any other IP should time out because of the security group.

Troubleshooting and clean-up

  • SSH times out to the web host: check the route table association, the public IP, and that your current IP matches the security group rule.
  • Permission denied (publickey): wrong user name (Amazon Linux uses ec2-user) or key file permissions too open.
  • Private host needs updates: in real designs you add a NAT gateway or VPC endpoints, but both cost money, so skip them here.

Delete in reverse order, because AWS will not delete a VPC that still contains resources. Then check the console in this region shows no instances, volumes, Elastic IPs or VPC called lab-vpc.

aws ec2 terminate-instances --instance-ids <web-id> <app-id>
aws ec2 wait instance-terminated --instance-ids <web-id> <app-id>
aws ec2 delete-security-group --group-id $SGA
aws ec2 delete-security-group --group-id $SGW
aws ec2 delete-key-pair --key-name lab-key
aws ec2 disassociate-route-table --association-id <association-id>
aws ec2 delete-route-table --route-table-id $RT
aws ec2 detach-internet-gateway --internet-gateway-id $IGW --vpc-id $VPC
aws ec2 delete-internet-gateway --internet-gateway-id $IGW
aws ec2 delete-subnet --subnet-id $PUB
aws ec2 delete-subnet --subnet-id $PRIV
aws ec2 delete-vpc --vpc-id $VPC

Practice

What makes a subnet public in AWS?
Its route table has a default route to an internet gateway, and instances in it have public IP addresses.
Why does the app security group reference the web security group instead of an IP?
It allows SSH only from instances in the web group, whatever their IPs, which is tighter and survives address changes.
Why does this lab avoid a NAT gateway?
NAT gateways are billed per hour and per GB, and are easy to forget; they are not needed to learn the routing concept.

Key takeaways

  • A public subnet is defined by its route to an internet gateway.
  • Security groups are stateful and can reference other groups.
  • Reach private instances through a jump host or Session Manager.
  • NAT gateways and public IPv4 addresses cost money in labs.
  • Delete in reverse order and check the region is empty.
Tracks your learning on this device only. See your progress on the learning paths.

About the author

Mohamed Fayas

Founder of FM TecHub. CCNA certified, with a BTEC HND in Network Engineering, and BEng (Hons) in Computer Networking and Cloud Security. He writes practical, plain-English guides on networking, security and cloud.

More about the author

Original content written by Mohamed Fayas for FM TecHub. © 2026 FM TecHub. You're welcome to share a link to this page; please don't copy the text elsewhere without permission.

Stay up to date by email

New notes, tools and security news from FM TecHub, explained simply. Free.

We only use your email to send these updates. See our privacy policy.