Build a Secure VPC from Scratch

Create an isolated AWS network with subnets, firewalls, and a live web server.

Introduction

30 Second Summary

Every building has layers of security. A lobby door, a key card scanner, a locked office. Cloud networks work the same way.

In this project, you will build a secure cloud network on AWS from scratch. You will create your own isolated network, divide it into public and private zones, and configure two layers of firewalls to control who gets in.

What You'll Build

A production-style secure network architecture on AWS using a Virtual Private Cloud, subnets, Security Groups, and Network ACLs.

By the end of this project, you'll have:

  • A custom VPC segmented into a public subnet (internet-accessible) and a private subnet (isolated from the internet).
  • Two layers of firewalls: Security Groups at the instance level and Network ACLs at the subnet level, controlling traffic in and out.
  • A running EC2 instance in the public subnet that serves a web page, proving your network works end-to-end.
  • Secret Mission: Block specific traffic using a NACL deny rule and observe how subnet-level firewalls can override instance-level permissions.

Are there any prerequisites?

None. This project includes full AWS account setup from scratch. All you need is a web browser and an email address.

Create Your VPC and Subnets

Your AWS account is set up and your credits are confirmed. Now it's time to build the actual network your cloud resources will live inside.

In this step, you'll create a Virtual Private Cloud and divide it into two subnets. One subnet will have internet access (public) and one will be completely isolated (private).

In this step, get ready to:
  • Create a custom VPC with a public subnet and a private subnet.
  • Review the resources AWS generates automatically.
  • Verify the route tables direct traffic correctly.
Create the VPC and subnets

AWS lets you create a VPC and all its networking components in one wizard. You'll configure your network's address range, subnet layout, and connectivity all at once.

  • In the AWS Console search bar at the top, type VPC.
  • Select VPC from the results.
  • On the VPC Dashboard, click Create VPC.
  • For Resources to create, select VPC and more.
  • Under Name tag auto-generation, type secure-lab.
  • For IPv4 CIDR block, enter 10.0.0.0/16.
  • For Number of Availability Zones, select 1.
  • For Number of public subnets, select 1.
  • For Number of private subnets, select 1.
  • Expand Customize subnet CIDR blocks.
  • For the public subnet CIDR, enter 10.0.1.0/24.
  • For the private subnet CIDR, enter 10.0.2.0/24.
  • For NAT gateways, select None.
  • For VPC endpoints, select None.
  • Under DNS options, confirm both Enable DNS hostnames and Enable DNS resolution are checked.
  • Click Create VPC.

What do these settings mean?

The value 10.0.0.0/16 uses CIDR notation. The /16 means the first 16 bits of the address are fixed, giving your VPC 65,536 possible IP addresses to work with.

Your subnets use /24, which gives 256 addresses each. 10.0.1.0/24 is for public (internet-accessible) resources. 10.0.2.0/24 is for private (isolated) resources.

NAT gateways let private subnets reach the internet, but they cost money. Skipping them keeps this project free and makes the private subnet truly isolated.

Review what AWS created

After clicking Create VPC, AWS shows you a resource map of everything it built. You should see all components marked with a green success status.

What was created?

The wizard created five types of resources in one step:

  • 1 VPC (secure-lab-vpc) with CIDR 10.0.0.0/16. This is your isolated network.
  • 1 public subnet (secure-lab-subnet-public1) with CIDR 10.0.1.0/24. Resources here can reach the internet.
  • 1 private subnet (secure-lab-subnet-private1) with CIDR 10.0.2.0/24. Resources here are completely isolated.
  • 1 Internet Gateway (secure-lab-igw) attached to the VPC. This is the door between your VPC and the public internet.
  • 2 route tables: one for the public subnet (with a route to the Internet Gateway) and one for the private subnet (local traffic only).

What makes a subnet public or private?

A public subnet has a route table entry that sends internet-bound traffic to an Internet Gateway. A private subnet has no such route, so its resources cannot reach the internet directly.

Verify the route tables

Route tables are the traffic rules for your subnets. Each route table entry says "if traffic is going to this destination, send it here."

  • In the left navigation pane, click Route tables.
  • Find the route table tagged secure-lab-rtb-public.
  • Click on it, then click the Routes tab.
  • Confirm you see two routes:
  • Now find secure-lab-rtb-private and click on it.
  • Click the Routes tab and confirm it has only one route:

Why does the private route table have no internet route?

The private subnet's route table only knows about local VPC traffic. Without a route to the Internet Gateway, any resource in that subnet cannot send or receive traffic from the public internet. This is how you keep sensitive resources like databases isolated.

Don't see two route tables?

Make sure you selected VPC and more (not VPC only) when creating the VPC. If you chose VPC only, AWS only creates the VPC without subnets, route tables, or an Internet Gateway.

If this happened, delete the VPC and start substep 1 again. Help me fix my VPC setup.

Your VPC is built and your traffic rules are in place. Next up, you'll configure the firewalls that decide which traffic is actually allowed through.

Configure Security Groups

Your VPC now has a clearly segmented network. The public subnet can reach the internet and the private subnet stays isolated.

But right now, any traffic can flow freely to any resource inside those subnets. Security Groups act as firewalls at the instance level, letting you control exactly which ports and IP addresses are allowed to reach your servers.

In this step, get ready to:
  • Create a Security Group that allows HTTP and SSH traffic to your web server.
  • Understand how stateful firewalls automatically allow response traffic.
  • Review the default Security Group to see how AWS handles resources without a custom group.
Create a Security Group for web server access

A Security Group defines which inbound and outbound traffic is allowed to reach a specific resource. You will create one that permits web traffic (HTTP) from anyone and SSH access only from your computer.

  • In the VPC console left navigation pane, click Security groups.
  • Click Create security group.
  • In the Security group name field, type web-server-sg.
  • In the Description field, type Allow HTTP and SSH access to web server.
  • In the VPC dropdown, select secure-lab-vpc.

Now you will add the inbound rules that define which traffic can reach your server.

  • Under Inbound rules, click Add rule.
  • For the first rule, set Type to HTTP.
  • Set Source to Anywhere-IPv4 (0.0.0.0/0). This allows anyone on the internet to view your web page.
  • Click Add rule again to add a second rule.
  • For the second rule, set Type to SSH.
  • Set Source to My IP. AWS auto-fills your current IP address so only your computer can connect via SSH.

Leave the Outbound rules section as the default (All traffic to 0.0.0.0/0). This allows your server to send responses back to any request it receives.

  • Click Create security group.

You should see a success banner confirming your security group was created. The details page shows web-server-sg with two inbound rules listed.

Why restrict SSH to My IP?

SSH gives full command-line access to your server. Opening it to the entire internet (0.0.0.0/0) would let anyone attempt to log in.

By choosing My IP, AWS restricts SSH access to your current IP address only. This is a security best practice for protecting remote access.

Don't see secure-lab-vpc in the VPC dropdown?

Confirm your region is set to US East (N. Virginia) us-east-1 in the top-right corner of the console. Resources only appear in the region where they were created.

help me find my VPC when creating a security group

Understand stateful behavior

Security Groups are stateful. This is a critical concept that separates them from the Network ACLs you will configure in the next step.

What does stateful mean?

When a Security Group allows inbound traffic on a port (like HTTP on port 80), the response back to the user is automatically allowed. You do NOT need to create a separate outbound rule for that response.

Think of it like a bouncer at a door. If someone is allowed in, they are automatically allowed back out. Network ACLs work differently. They are stateless, meaning you must explicitly allow traffic in both directions. You will see this contrast in the next step.

Review the default Security Group

Every VPC comes with a default Security Group. Understanding its rules helps you see why creating a custom group is important.

  • Click Security groups in the left navigation pane to return to the list.
  • Find the security group named default that belongs to secure-lab-vpc.
  • Click on it to open its details.
  • Click the Inbound rules tab.

You should see that the default group allows all inbound traffic from resources that share the same security group. The Outbound rules tab shows all traffic allowed to anywhere.

Why does this matter?

The default security group is automatically assigned to resources unless you specify a different one. Its permissive rules are fine for internal VPC communication, but your web server needs tighter controls.

That is why you created web-server-sg with explicit rules for only HTTP and SSH. When you launch your EC2 instance later, you will assign this custom group instead of the default.

Your instance-level firewall is locked down. Next up, you will add a second layer of defense at the subnet level using Network ACLs.

Configure Network ACLs

In the previous step, you configured a Security Group to control traffic at the instance level. That gives you one layer of protection.

Now you will add a second layer of defense at the subnet level using a Network ACL. Network ACLs are stateless firewalls, meaning they do not remember whether traffic was previously allowed in. You must explicitly allow traffic in both directions. This gives you fine-grained control and a safety net in case your Security Group rules are accidentally changed.

In this step, get ready to:
  • View the default Network ACL and understand its rules.
  • Create a custom Network ACL with specific allow rules for HTTP, SSH, and return traffic.
  • Associate the custom Network ACL with your public subnet.
View the default Network ACL

Every VPC comes with a default Network ACL that allows all traffic. Before creating your own, take a look at what the default one does.

  • In the left navigation pane of the VPC console, click Network ACLs.
  • Find the Network ACL associated with secure-lab-vpc. It is labeled as the default.
  • Click on the Network ACL to select it.
  • Click the Inbound rules tab.
  • Confirm you see Rule 100 allowing ALL traffic from 0.0.0.0/0, and Rule * denying ALL traffic (the catch-all).
  • Click the Outbound rules tab and confirm the same pattern.

What does the default Network ACL do?

The default Network ACL allows all traffic in and out. It exists so that your subnets are not accidentally locked down when they are first created. You will now create a custom Network ACL that is much more restrictive.

Create a custom Network ACL for the public subnet

Custom Network ACLs start by denying all traffic. You will add rules to allow only the traffic your web server needs.

  • Click Create network ACL.
  • For Name, enter public-subnet-nacl.
  • For VPC, select secure-lab-vpc.
  • Click Create network ACL.

Your new NACL appears in the list. By default, it denies all inbound and outbound traffic. Now you will add the rules it needs.

  • Select public-subnet-nacl from the list.
  • Click the Inbound rules tab, then click Edit inbound rules.
  • Click Add new rule and enter the following for Rule 1:
  • Rule number: 100.
  • Type: HTTP (80).
  • Source: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Add new rule again and enter Rule 2:
  • Rule number: 110.
  • Type: SSH (22).
  • Source: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Add new rule again and enter Rule 3:
  • Rule number: 120.
  • Type: Custom TCP.
  • Port range: 1024-65535.
  • Source: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Save changes.

Why allow ports 1024-65535?

Network ACLs are stateless. When a client connects to your web server on port 80, the server responds from a random high port (called an ephemeral port) between 1024 and 65535. Without this rule, the response would be blocked even though the request was allowed in.

This is the key difference from Security Groups. Security Groups are stateful and automatically allow return traffic. Network ACLs require you to explicitly allow traffic in both directions.

Now add the outbound rules so your server can send responses.

  • Click the Outbound rules tab, then click Edit outbound rules.
  • Click Add new rule and enter Rule 1:
  • Rule number: 100.
  • Type: HTTP (80).
  • Destination: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Add new rule again and enter Rule 2:
  • Rule number: 110.
  • Type: HTTPS (443).
  • Destination: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Add new rule again and enter Rule 3:
  • Rule number: 120.
  • Type: Custom TCP.
  • Port range: 1024-65535.
  • Destination: 0.0.0.0/0.
  • Allow/Deny: Allow.
  • Click Save changes.

Why allow HTTPS outbound?

Your server may need to reach external HTTPS endpoints for software updates or API calls. Allowing outbound port 443 ensures your server can communicate securely with the internet when needed.

Associate the NACL with the public subnet

Your custom NACL has rules, but it is not protecting anything yet. You need to associate it with the public subnet so its rules take effect.

  • Click the Subnet associations tab.
  • Click Edit subnet associations.
  • Check the box next to secure-lab-subnet-public1.
  • Click Save changes.

The Subnet associations tab now shows secure-lab-subnet-public1 listed under your custom NACL.

What happened to the default NACL?

A subnet can only be associated with one Network ACL at a time. When you associated public-subnet-nacl with the public subnet, the previous association with the default NACL was automatically removed.

Public subnet not showing in the association list?

Make sure you selected secure-lab-vpc when you created the NACL. If you accidentally chose a different VPC, the subnet will not appear. Delete the incorrect NACL and create a new one in the correct VPC.

Help me associate my NACL with the correct subnet.

Your network now has two layers of firewalls. Next up, you will launch a web server inside this protected subnet and prove the whole architecture works end-to-end.

Launch an EC2 Instance and Test Connectivity

You have a fully configured network with two layers of firewalls in place. But a firewall protecting an empty room is not very useful.

In this step, you will launch an EC2 instance (a virtual server) inside your public subnet. Then you will access it from your browser to prove that every layer of your network architecture works together end-to-end.

In this step, get ready to:
  • Launch an EC2 instance in your public subnet with a web server startup script.
  • Test HTTP connectivity by visiting the server's public IP in your browser.
  • Trace how your request passes through the Internet Gateway, Network ACL, and Security Group.
Launch an EC2 instance in the public subnet

Your network is ready to host a server. You will launch a t3.micro instance running Amazon Linux 2023, and attach a startup script that automatically installs a web server when the instance boots.

  • In the AWS Console search bar at the top, type EC2 and select EC2.
  • Click Launch instance.

The launch instance wizard opens with several configuration sections. Fill them out one at a time.

  • Under Name and tags, for Name, enter secure-lab-webserver.
  • Under Application and OS Images (Amazon Machine Image), confirm that Amazon Linux 2023 AMI is selected. It should be the default and marked Free tier eligible.
  • Under Instance type, select t3.micro (Free tier eligible).

What is an instance type?

An instance type defines how much CPU and memory your server gets. t3.micro gives you 2 vCPUs and 1 GB of memory. It is more than enough for a test web server.

  • Under Key pair (login), click Create new key pair.
  • For Key pair name, enter secure-lab-key.
  • Keep the key pair type as RSA and the file format as .pem.
  • Click Create key pair.

What just downloaded?

Your browser automatically downloaded a file called secure-lab-key.pem. This is your private key file. It is the only way to connect to your instance via SSH later. Save it somewhere safe and do not share it.

  • Under Network settings, click Edit.
  • For VPC, select secure-lab-vpc.
  • For Subnet, select secure-lab-subnet-public1.
  • For Auto-assign public IP, select Enable.
  • For Firewall (security groups), choose Select existing security group.
  • Under Common security groups, select web-server-sg.

Now you will add a startup script that automatically installs a web server when the instance launches.

  • Expand Advanced details at the bottom of the page.
  • Scroll down to the User data field.
  • Paste the following script into the User data field:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from your secure VPC!</h1><p>This server is running in a public subnet protected by Security Groups and Network ACLs.</p>" > /var/www/html/index.html

What does this script do?

This is a User Data script. EC2 runs it automatically as the root user when the instance first boots.

  • yum update -y updates all installed packages.
  • yum install -y httpd installs the Apache web server.
  • systemctl start httpd starts the web server immediately.
  • systemctl enable httpd ensures it starts on every future reboot.
  • The final echo line creates a simple HTML page you can visit to confirm everything works.
  • Click Launch instance.

You should see a success message. Click the instance ID link to go to the Instances page.

Instance failed to launch?

  • Make sure you selected secure-lab-vpc (not the default VPC) under Network settings.
  • Confirm that Auto-assign public IP is set to Enable. Without a public IP, you cannot reach the instance from the internet.
  • Check that the security group web-server-sg appears in the list. If you do not see it, confirm you selected the correct VPC first.

help me troubleshoot my EC2 instance launch failure in my secure-lab-vpc

Test HTTP connectivity

Your instance needs about 2 minutes to boot and run the startup script. You will know it is ready when the Instance state shows Running and the Status check column shows 2/2 checks passed.

  • On the Instances page, click on secure-lab-webserver to open its details.
  • Find the Public IPv4 address in the instance details panel (for example, 3.xx.xx.xx).
  • Copy the IP address.
  • Open a new browser tab and paste the following URL, replacing the placeholder with your actual IP address:
http://[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]

You should see a page that says "Hello from your secure VPC!" in large text.

Page not loading or timing out?

  • Make sure you are using http:// (not https://). Your Security Group only allows port 80 (HTTP), not port 443 (HTTPS).
  • Wait a full 2 minutes after launch. The user data script takes time to install the web server.
  • Go back to EC2 and confirm the instance has a Public IPv4 address listed. If it says a dash (-), Auto-assign public IP was not enabled.
  • Verify the NACL inbound Rule 100 allows HTTP (port 80) from 0.0.0.0/0.

help me figure out why my EC2 web server page is not loading

Understand what happened

When you loaded that web page, your request passed through every layer of your network architecture. Here is the full path your HTTP request took.

How did my request reach the server?

  • Your browser sent an HTTP request to the instance's public IP address.
  • The Internet Gateway forwarded the request into your VPC.
  • The Network ACL on the public subnet checked its inbound rules. Rule 100 allows HTTP. The request passed.
  • The Security Group on the instance checked its inbound rules. HTTP from anywhere is allowed. The request passed.
  • The web server responded. Because Security Groups are stateful, the response was automatically allowed out without needing a separate outbound rule.
  • The NACL checked outbound traffic. Rule 120 allows ephemeral port traffic (1024-65535). The response left the subnet and reached your browser.

Why does the NACL need an ephemeral port rule?

Network ACLs are stateless. They do not remember that an inbound request arrived on port 80. When the server sends a response, it uses a random high port (between 1024 and 65535). Without Rule 120 allowing these ports outbound, the response would be silently dropped at the subnet boundary.

Secret mission

Block Traffic with a NACL Deny Rule

Your Security Group allows HTTP traffic. But what if you add a DENY rule at the subnet level? In this mission, you will manipulate NACL rule ordering to prove that subnet-level firewalls can override instance-level permissions.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your EC2 instance consumes credits while running, so cleaning up prevents unnecessary credit usage.

Cost warning

Your t3.micro EC2 instance uses credits while it is running. Your $100 in sign-up credits cover this easily for a few days of experimentation, but leaving the instance running for weeks will deplete your credit balance. If you are done with this project, choose Pause or Delete below.

Resources you used:

  • 1 VPC (secure-lab-vpc).
  • 2 Subnets (secure-lab-subnet-public1 and secure-lab-subnet-private1).
  • 1 Internet Gateway (secure-lab-igw).
  • 2 Route tables (secure-lab-rtb-public and secure-lab-rtb-private).
  • 1 Security Group (web-server-sg).
  • 1 Network ACL (public-subnet-nacl).
  • 1 EC2 instance (secure-lab-webserver, t3.micro).
  • 1 Key pair (secure-lab-key) and the downloaded .pem file on your local machine.

Keep everything running

No action needed. Choose this if you are still actively experimenting with your VPC and want to continue testing different Security Group or NACL configurations.

  • Your EC2 instance will continue consuming credits while it runs.
  • Your $100 in credits covers a t3.micro instance for many days, so short-term experimentation is fine.
  • The VPC, subnets, route tables, security groups, and NACLs do not cost anything on their own.
  • The downloaded secure-lab-key.pem file remains on your local machine.

Pause - I'll come back to this later

Stop the EC2 instance to stop credit consumption while keeping all your network resources in place. You can restart the instance later to pick up where you left off.

  • In the AWS Console search bar, type EC2 and select EC2.
  • Click Instances in the left navigation.
  • Select the checkbox next to secure-lab-webserver.
  • Click Instance state, then click Stop instance.
  • Confirm by clicking Stop.

What stays after stopping?

A stopped instance does not consume compute credits. However, the attached EBS storage volume remains and uses a small amount of your credit balance. Your VPC, subnets, route tables, security groups, and NACLs remain intact at no cost.

Delete - I don't want to use this again

Remove all project resources completely. This is the recommended option if you are finished with this project.

Follow these steps in order:

  • Terminate the EC2 instance:
  • In the AWS Console search bar, type EC2 and select EC2.
  • Click Instances in the left navigation.
  • Select the checkbox next to secure-lab-webserver.
  • Click Instance state, then click Terminate instance.
  • Confirm termination.
  • Delete the VPC (this removes subnets, route tables, the internet gateway, and security groups automatically):
  • In the AWS Console search bar, type VPC and select VPC.
  • Click Your VPCs in the left navigation.
  • Select secure-lab-vpc.
  • Click Actions, then click Delete VPC.
  • Type delete in the confirmation field and confirm.
  • Delete the custom Network ACL (if it was not removed with the VPC):
  • In the VPC console, click Network ACLs in the left navigation.
  • If public-subnet-nacl still appears, select it.
  • Click Actions, then click Delete network ACL.
  • Confirm deletion.
  • Delete the key pair:
  • In the AWS Console search bar, type EC2 and select EC2.
  • Click Key Pairs in the left navigation under Network & Security.
  • Select secure-lab-key.
  • Click Actions, then click Delete.
  • Confirm deletion.
  • Delete the local .pem file:
  • Find the secure-lab-key.pem file wherever you saved it (likely your Downloads folder).
  • Delete the file permanently.

Nice Work!

Nice Work!

You just built a production-style secure network architecture on AWS from scratch. Your custom VPC is segmented, firewalled at two layers, and serving live traffic through a verified web server.

You've learned how to:

  • Build a custom VPC with public and private subnets, an Internet Gateway, and route tables that control which subnets can reach the internet.
  • Configure two layers of firewalls: a Security Group (stateful, instance-level) and a custom Network ACL (stateless, subnet-level) to control traffic in both directions.
  • Launch an EC2 instance in a public subnet and verify end-to-end connectivity through both firewall layers.
  • Secret Mission: Proved that NACL deny rules override Security Group allows by manipulating rule ordering, demonstrating that lower-numbered rules are evaluated first.

Ready to quiz yourself?