Design a 3-Tier Architecture on AWS

Build a highly available 3-tier VPC with an ALB, private EC2s, and RDS.

Introduction

30 Second Summary

Every time you check your bank balance or browse an online store, your request flows through separate layers of infrastructure. This layered design is what keeps the service running even when parts of it fail.

In this project, you will design and build a highly available 3-tier architecture on AWS. You will set up a VPC with public and private subnets, deploy app servers behind a load balancer, and connect them to an isolated database.

What You'll Build

Paste the load balancer's public URL into your browser and watch live responses alternate between two Availability Zones as traffic flows through the full three-tier stack.

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

  • A live URL you can paste into your browser to see responses from app servers hidden in private subnets. Each page refresh shows traffic distributing across both Availability Zones, proving the architecture survives a zone failure.
  • A failover test where you stop one app server and watch the Application Load Balancer continue serving traffic from the surviving AZ without any downtime.
  • Full network segmentation you can verify. Each tier's security group only allows connections from the tier above it, and the RDS MySQL database sits in completely isolated subnets.
  • Secret Mission: Replace manually launched EC2 instances with an Auto Scaling Group so the app tier self-heals when an instance fails.

Are there any prerequisites?

You will need an AWS account with Free Tier eligibility. This project assumes you are comfortable navigating core AWS services like VPC, IAM, EC2, and RDS.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

You're designing a complete, highly available 3-tier network architecture on AWS. Understanding the full picture now will help every configuration decision make sense as you build.

Build the Network Foundation

Every 3-tier architecture starts with network isolation. Without separate subnets for each tier, all your resources share the same security boundary. This defeats the entire purpose of a tiered design.

In this step, you'll use the AWS VPC wizard to create your entire network foundation in one shot. By the end, you'll have a VPC with six subnets spanning two Availability Zones, an Internet Gateway, and route tables to control traffic flow.

In this step, get ready to:
  • Create a VPC with 6 subnets, an Internet Gateway, and route tables using the VPC wizard.
  • Rename private subnets to distinguish app-tier from data-tier.
  • Verify route tables are correctly configured for public and private traffic.
Create the VPC using the wizard

The VPC console has a VPC and more wizard that creates everything you need in one step. It builds the VPC, subnets, Internet Gateway, and route tables all at once.

  • In the AWS Management Console, type VPC in the search bar at the top and select VPC from the results.
  • On the VPC dashboard, click Create VPC.
  • Under Resources to create, select VPC and more.

What does VPC and more do?

The VPC and more option creates a VPC along with all its supporting networking resources in a single step. Instead of manually creating each subnet, route table, and gateway individually, the wizard handles the entire setup.

  • Under Name tag auto-generation, enter three-tier-vpc.
  • For IPv4 CIDR block, enter 10.0.0.0/16.
  • For Number of Availability Zones (AZs), select 2.
  • For Number of public subnets, select 2.
  • For Number of private subnets, select 4.
  • For NAT gateways, select None.
  • For VPC endpoints, select None.

Why skip NAT gateways?

NAT gateways allow instances in private subnets to reach the internet for outbound traffic like software updates. They cost approximately $0.045 per hour each, so they are not covered by the Free Tier.

In this project, your app servers use Python's built-in http.server module, which is pre-installed on Amazon Linux 2023. No package downloads are needed, so skipping the NAT gateway keeps the project free.

  • Check the Preview pane on the right side of the page. You should see a diagram showing your VPC, 6 subnets across 2 AZs, 3 route tables, and an Internet Gateway.
  • Click Create VPC.

The wizard takes a few seconds to create all the resources. You should see a green success banner listing everything that was created.

What is a CIDR block?

A CIDR block defines a range of IP addresses for your network. 10.0.0.0/16 gives you 65,536 addresses (from 10.0.0.0 to 10.0.255.255). The wizard automatically divides this range into smaller blocks for each subnet.

Rename subnets for clarity

The wizard created 4 private subnets with auto-generated names, but those names do not distinguish the app tier from the data tier. Renaming them now prevents confusion when you launch resources into specific subnets later.

  • In the left navigation pane, click Subnets.
  • In the search bar, type three-tier-vpc to filter your subnets. You should see all 6 subnets the wizard created.
  • Sort the list by the Name column to see the auto-generated names in order.

You will see 4 private subnets with names containing private and a number (1 through 4). The wizard assigns them in alternating Availability Zones.

  • Find the private subnet with private1 in its name. Click the pencil (edit) icon next to its name.
  • Replace the name with App-Subnet-AZ1 and click the checkmark to save.
  • Find the private subnet with private2 in its name. Rename it to App-Subnet-AZ2.
  • Find the private subnet with private3 in its name. Rename it to Data-Subnet-AZ1.
  • Find the private subnet with private4 in its name. Rename it to Data-Subnet-AZ2.

Confirm the Availability Zone

After renaming each subnet, glance at the Availability Zone column to verify that AZ1 subnets are in one zone and AZ2 subnets are in the other. The wizard alternates zones, so the numbered order matches AZ order.

Your subnets list should now clearly show which subnets belong to the app tier and which belong to the data tier.

Don't see 6 subnets?

Make sure the search bar filter shows three-tier-vpc and not a different VPC. Your account may have a default VPC with its own subnets. Clear the filter and check the VPC column to identify which subnets belong to three-tier-vpc.

Help me find my VPC subnets in the console.

Verify routing

Route tables control where network traffic goes. Public subnets need a route to the Internet Gateway so they can reach the internet. Private subnets should only have a local route, keeping them isolated from direct internet access.

  • In the left navigation pane, click Route tables.
  • Filter by three-tier-vpc in the search bar. You should see 3 route tables.
  • Click on the route table whose Name column contains public.
  • Select the Routes tab at the bottom of the page.

You should see two routes: a local route for 10.0.0.0/16 and a route for 0.0.0.0/0 pointing to an Internet Gateway (its target starts with igw-). The 0.0.0.0/0 route is what makes these subnets "public." It sends all non-local traffic to the Internet Gateway.

  • Go back to the route tables list and click on one of the route tables whose name contains private.
  • Select the Routes tab.

You should see only the local route for 10.0.0.0/16. There is no 0.0.0.0/0 route because these subnets have no path to the internet. This is by design. Private subnets are isolated.

What do these routes mean?

The 10.0.0.0/16 local route lets resources within the VPC talk to each other. Every route table has this route automatically.

The 0.0.0.0/0 route is a "catch-all" that sends all other traffic to the Internet Gateway. Only the public subnets have this route. Without it, a subnet is private because it has no way to reach the internet directly.

Before you check the VPC dashboard, predict: how many subnets, route tables, and Internet Gateways do you expect to see listed for your VPC?

  • In the left navigation pane, click Your VPCs.
  • Select three-tier-vpc and click the Resource map tab.

The Resource map shows a visual diagram of your entire VPC. You should see 1 VPC, 6 subnets across 2 Availability Zones, 1 Internet Gateway, and 3 route tables. The public subnets appear in green, and the private subnets appear in blue.

Route table count looks different?

Make sure you are filtering by three-tier-vpc and not looking at route tables from a default VPC. Your account's default VPC has its own route table that is separate from this project.

Help me verify my VPC route tables are correct.

Your network foundation is locked in. Next up, you'll launch EC2 instances into the private app subnets and discover why they can't be reached from the internet.

Launch the App Tier

Your network is ready. Six subnets, two Availability Zones, and route tables that keep public and private traffic separate.

Now it's time to put something inside those subnets. In this step, you'll launch two EC2 instances running a lightweight Python web server in the private app subnets. Here's the twist: the instances will be alive and running, but completely unreachable from the internet.

In this step, get ready to:
  • Create a security group for the app tier.
  • Launch two EC2 instances in private subnets with a Python web server.
  • Confirm the instances are running but unreachable from the internet.
Create the app-tier security group

Before launching any instances, you need a security group to control which traffic can reach them. Think of a security group as a virtual firewall that sits in front of your instances.

  • In the AWS Management Console, type VPC in the top search bar and select VPC.
  • In the left sidebar, click Security groups.
  • Click Create security group.
  • In the Security group name field, enter App-SG.
  • In the Description field, enter App tier security group.
  • For VPC, remove the default VPC and select three-tier-vpc.
  • Under Inbound rules, click Add rule.
  • Set Type to HTTP.
  • Set Source to Custom and enter 10.0.0.0/16.
  • Leave Outbound rules as the default (all traffic allowed).
  • Click Create security group.

You should see a green success banner confirming that App-SG was created.

What does this security group do?

App-SG allows inbound HTTP traffic (port 80) only from IP addresses inside the VPC (10.0.0.0/16). No traffic from the public internet can reach these instances directly.

You'll refine this rule later to allow traffic only from the load balancer's security group instead of the entire VPC range.

Don't see three-tier-vpc in the VPC dropdown?

Make sure you are in the same AWS Region where you created the VPC in Step 1. Check the Region selector in the top right corner of the console.

help me find my VPC when creating a security group

Launch EC2 instances in the private subnets

Each app-tier instance runs a Python web server that displays its instance ID and Availability Zone. You'll launch one instance per app subnet so the architecture spans both AZs.

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

Configure the first instance with these settings:

  • In the Name field, enter App-Instance-AZ1.
  • Under Application and OS Images, select Amazon Linux 2023 AMI (marked Free tier eligible).
  • Under Instance type, confirm t2.micro is selected (Free tier eligible).
  • Under Key pair (login), select Proceed without a key pair (Not recommended). You won't need SSH access for this project.

Why no key pair?

Key pairs let you SSH into an instance. These instances sit in private subnets with no internet route, so SSH from your laptop is not possible anyway. The web server starts automatically via User Data, so there is nothing to log in and configure.

Now configure the network settings:

  • Under Network settings, click Edit.
  • For VPC, select three-tier-vpc.
  • For Subnet, select App-Subnet-AZ1.
  • Confirm Auto-assign public IP is set to Disable. This is the default for private subnets.
  • Under Firewall (security groups), select Select existing security group.
  • In the Common security groups dropdown, select App-SG.

Finally, add the User Data script that starts the web server when the instance boots:

  • 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
INSTANCE_ID=$(ec2-metadata -i | cut -d ' ' -f 2)
AZ=$(ec2-metadata -z | cut -d ' ' -f 2)
cat <<EOF > /var/www/html/index.html
<html>
<head><title>3-Tier App</title></head>
<body>
<h1>Hello from the App Tier!</h1>
<p>Instance ID: $INSTANCE_ID</p>
<p>Availability Zone: $AZ</p>
</body>
</html>
EOF
mkdir -p /var/www/html
cat <<EOF > /var/www/html/index.html
<html>
<head><title>3-Tier App</title></head>
<body>
<h1>Hello from the App Tier!</h1>
<p>Instance ID: $INSTANCE_ID</p>
<p>Availability Zone: $AZ</p>
</body>
</html>
EOF
cd /var/www/html
python3 -m http.server 80 &

What does this User Data script do?

User Data is a script that runs automatically the first time an EC2 instance boots. This script does three things:

  • ec2-metadata -i and ec2-metadata -z grab the instance's ID and Availability Zone from the instance metadata service.
  • It creates an HTML page that displays these values so you can tell which instance is responding.
  • python3 -m http.server 80 starts Python's built-in web server on port 80, serving that HTML page. No package installation needed because Python 3 is pre-installed on Amazon Linux 2023.

The yum update -y line will fail silently because the private subnets have no internet access (no NAT gateway). This is fine because the rest of the script uses tools already installed on the AMI.

  • Click Launch instance.

You should see a success message confirming that App-Instance-AZ1 is launching.

Now launch the second instance in the other Availability Zone:

  • Click Launch instance again from the success page, or navigate to Instances in the left sidebar and click Launch instance.
  • Use the exact same settings as the first instance, with two changes:
  • Set Name to App-Instance-AZ2.
  • Set Subnet to App-Subnet-AZ2.
  • Everything else stays the same: t2.micro, no key pair, App-SG, and the same User Data script.
  • Click Launch instance.

You should see a second success message confirming that App-Instance-AZ2 is launching.

Seeing an error about instance limits?

New AWS accounts sometimes have a limit on the number of running instances. If you see a vCPU limit error, check the Service Quotas console for Running On-Demand Standard instances and request an increase if needed.

help me resolve an EC2 instance launch limit error

Confirm instances are running but unreachable
  • In the left sidebar, click Instances.
  • Wait until both App-Instance-AZ1 and App-Instance-AZ2 show Running in the Instance state column. This takes about 1-2 minutes.

Both servers are alive and running a web server on port 80. But can you actually reach them?

  • Click on App-Instance-AZ1 to open its details.
  • Under the Details tab, find Private IPv4 address. Notice the instance has a private IP (something like 10.0.x.x) but no Public IPv4 address.

Before you try anything, think about this: do you think you could reach this instance by typing its private IP into your browser? Why or why not?

Try pasting the private IP address into your browser's address bar. The page will time out. This is expected.

Why can't you reach the instances?

Two things block you. First, the instances have no public IP address, so the internet has no address to route traffic to. Second, the private subnets have no route to an Internet Gateway, so even if you could send a request in, the instances couldn't send a response back.

This isolation is intentional. In a 3-tier architecture, app servers should never be directly exposed to the internet. You need something in the public subnets to accept internet traffic and forward it to the app tier. That something is a load balancer, and it's exactly what you'll build next.

Your app servers are alive but trapped behind private subnets with no way in from the internet. In the next step, you'll add an Application Load Balancer in the public subnets to give the outside world a front door to your app tier.

Add the Presentation Tier

Your app servers are running, but nobody can reach them. They sit in private subnets with no public IP, so there is no path from the internet into the app tier.

An Application Load Balancer solves this. It sits in the public subnets, accepts traffic from the internet, and forwards requests to your app servers in the private subnets. This is the presentation tier of your 3-tier architecture.

In this step, get ready to:
  • Create a security group that allows internet traffic to reach the ALB.
  • Create a Target Group and register your EC2 instances.
  • Launch an internet-facing Application Load Balancer.
Create the ALB security group

The ALB is the public-facing front door of your application. It needs its own security group that allows HTTP traffic from anywhere on the internet.

  • Return to the VPC console and select Security Groups in the left sidebar.
  • Click Create security group.
  • Enter ALB-SG in the Security group name field.
  • Enter a description such as Allows HTTP from the internet.
  • Select three-tier-vpc from the VPC dropdown.
  • Under Inbound rules, click Add rule.
  • Set Type to HTTP.
  • Set Source to Anywhere-IPv4 (0.0.0.0/0).
  • Click Create security group.

You should see ALB-SG in your security groups list alongside App-SG from earlier.

Why allow traffic from anywhere?

The ALB is the internet-facing entry point to your application. Just like a public website, it needs to accept HTTP requests from any IP address on the internet.

This is safe because the ALB only forwards traffic to registered targets. Your app servers remain hidden in private subnets, and the ALB acts as the gatekeeper.

Don't see three-tier-vpc in the dropdown?

Make sure you are in the same AWS Region where you created the VPC. Check the Region selector in the top-right corner of the console.

help me find my VPC in the security group creation form

Create a Target Group and register instances

A Target Group tells the ALB where to send incoming traffic. You register your EC2 instances as targets, and the ALB runs health checks against them to know which ones can handle requests.

  • Switch to the EC2 console.
  • In the left sidebar, scroll down to Load Balancing and select Target Groups.
  • Click Create target group.
  • Select Instances as the target type.
  • Enter App-TG for the Target group name.
  • Confirm Protocol is set to HTTP and Port is 80.
  • Select three-tier-vpc from the VPC dropdown.
  • Under Health checks, confirm the Health check path is set to /.

Now register your app-tier instances as targets.

  • Click Next to move to the Register targets page.
  • Select both app-tier EC2 instances from the list.
  • Click Include as pending below.
  • Click Create target group.

You should see App-TG in the target groups list. Click on it and select the Targets tab to confirm both instances are listed.

What is a health check?

The ALB periodically sends an HTTP request to the health check path (/) on each registered target. If a target responds with a success code, the ALB considers it healthy and sends it traffic.

If a target stops responding, the ALB marks it as unhealthy and stops sending requests to it. This is how the load balancer protects users from hitting a broken server.

Don't see your EC2 instances on the register targets page?

Make sure you selected three-tier-vpc as the VPC on the previous page. The register targets page only shows instances that belong to the selected VPC.

help me find my EC2 instances when creating a target group

Create the Application Load Balancer

The ALB sits in the public subnets and listens for incoming HTTP requests. When a request arrives, it forwards it to a healthy target in App-TG.

  • In the left sidebar, select Load Balancers under Load Balancing.
  • Click Create load balancer.
  • Find Application Load Balancer and click Create.
  • Enter three-tier-alb in the Load balancer name field.
  • Under Scheme, select Internet-facing.
  • Under Network mapping, select three-tier-vpc from the VPC dropdown.
  • Select both Availability Zones and choose the public subnet in each one (Public-Subnet-AZ1 and Public-Subnet-AZ2).
  • Under Security groups, remove the default security group and add ALB-SG.

Now configure the listener so the ALB knows where to forward traffic.

  • Under Listeners and routing, confirm the listener protocol is HTTP on port 80.
  • Set the Default action to forward to App-TG.
  • Click Create load balancer.

The ALB takes 1-2 minutes to provision. Wait until the State changes to Active.

  • Once active, select three-tier-alb from the load balancers list.
  • Find the DNS name field in the details panel and copy the value: your ALB DNS name

Why use the ALB's DNS name?

AWS assigns the ALB a public DNS name that routes to its nodes in the public subnets. This is the URL you share with users. Behind the scenes, the ALB distributes incoming requests across the healthy targets in your Target Group.

Before you open the ALB DNS in your browser, what do you think will happen? Will the page load and show a response from your app server?

  • Paste the ALB DNS name into your browser's address bar and press Enter.

The page does not load. You see an error or timeout. That is expected.

  • Return to the EC2 console and select Target Groups in the left sidebar.
  • Click on App-TG and select the Targets tab.

Both targets show as unhealthy. The ALB is trying to reach your app servers on port 80, but the traffic is not getting through. This is the gap you will fix in the next step.

Why is this happening?

The ALB, the Target Group, and the EC2 instances are all set up correctly. The problem is in the security group configuration between the ALB and the app tier. The next step digs into exactly what is wrong and teaches you the fix.

ALB stuck in Provisioning state?

ALB provisioning typically takes 1-2 minutes. If it has been longer than 5 minutes, check that you selected both public subnets (one per AZ) under Network mapping. Each subnet must have at least 8 free IP addresses.

help me troubleshoot my ALB that is stuck provisioning

Your ALB is live and pointing at your app servers, but traffic is not flowing. In the next step, you will diagnose the security group misconfiguration and fix it with one of the most important patterns in AWS networking.

Fix the Security Group Chain

You created the ALB in the previous step, but browsing to its DNS name returns errors. The App-TG Target Group shows both instances as "unhealthy."

The root cause is in App-SG. Right now it allows HTTP traffic from a broad CIDR range. In this step, you will replace that rule with a reference to ALB-SG directly. This pattern is called security group chaining, and it is the most important security pattern in a multi-tier architecture.

In this step, get ready to:
  • Update App-SG to allow traffic only from ALB-SG using security group chaining.
  • Verify the ALB health checks pass and your app responds through the load balancer.
Update App-SG to allow traffic from ALB-SG

The ALB sits in the public subnets and forwards requests to your app-tier instances on port 80. For that traffic to arrive, App-SG must explicitly allow inbound HTTP from the ALB's security group.

  • In the left sidebar of the EC2 console, click Security Groups under Network & Security.
  • Select the App-SG security group.
  • Click the Inbound rules tab.
  • Click Edit inbound rules.

You should see the existing HTTP rule with source 10.0.0.0/16. You need to replace this rule with one that references ALB-SG instead. The AWS console does not allow you to change a CIDR source to a security group source on an existing rule, so you will delete the old rule and add a new one.

  • Click Delete next to the existing HTTP rule.
  • Click Add rule.
  • Set Type to HTTP.
  • In the Source field, select Custom, then type ALB-SG in the search box and select the ALB-SG security group from the dropdown.
  • Click Save rules.

What is security group chaining?

Instead of allowing traffic from a broad IP range like 10.0.0.0/16, you set the source to another security group's ID. This means only resources attached to ALB-SG can reach your app servers on port 80.

This is the foundation of network security in a 3-tier architecture. Each tier only accepts traffic from the tier directly above it. The ALB accepts traffic from the internet, the app tier accepts traffic from the ALB, and the data tier accepts traffic from the app tier.

Confirm the Inbound rules tab now shows the HTTP rule with ALB-SG as the source instead of 10.0.0.0/16.

Still seeing the old CIDR rule?

Make sure you clicked Save rules after making changes. If you navigated away without saving, the old rule remains in place.

If you cannot find ALB-SG in the source dropdown, confirm you are editing the security group inside three-tier-vpc. Security groups from other VPCs will not appear.

help me fix the App-SG inbound rules to reference ALB-SG

Wait for health checks and verify traffic flows

The Application Load Balancer health checks run every 30 seconds. After a few consecutive checks pass, the Target Group marks each instance as "healthy."

  • In the left sidebar, click Target Groups under Load Balancing.
  • Select App-TG.
  • Click the Targets tab.
  • Wait 1 to 2 minutes, then refresh the page. Both targets should show a status of healthy.

Targets still showing unhealthy after 2 minutes?

Go back to Security Groups and confirm that App-SG has an inbound rule for HTTP on port 80 with ALB-SG as the source. If the source still shows 10.0.0.0/16, the change was not saved.

Also confirm both EC2 instances are still in the Running state. If an instance stopped, the health check will fail regardless of the security group.

help me debug why my ALB health checks are still failing

Before you open the ALB DNS name in your browser, what do you think you will see this time?

  • In the left sidebar, click Load Balancers under Load Balancing.
  • Select three-tier-alb.
  • Copy the DNS name from the Description section.
  • Paste the DNS name into your browser's address bar and press Enter.

You should see the page titled "3-Tier App" with "Hello from the App Tier!" along with the instance's ID and Availability Zone. Traffic is now flowing from the internet, through the ALB in the public subnets, to your EC2 instances in the private app subnets.

  • Refresh the page several times.

Watch the Availability Zone value change between refreshes. The ALB is distributing requests across both instances in different AZs.

Why does the Availability Zone change?

The ALB uses a round-robin algorithm by default. Each time you refresh, the request may go to a different instance. Seeing the AZ change proves that both instances are healthy and receiving traffic.

This is the presentation tier at work. The ALB is the only resource with a public-facing DNS name. Your app servers have no public IP addresses and are completely hidden from the internet.

Your presentation and app tiers are fully connected. Traffic flows from the internet through the ALB to your private app servers. Next up, you will complete the architecture by adding the data tier with an RDS database in isolated subnets.

Add the Data Tier

Your presentation and app tiers are working end-to-end. When you browse to the ALB DNS name, traffic flows through the public subnets to your private EC2 instances, and you see a different Availability Zone each time you refresh.

Now it is time to complete the architecture with the data tier. You will add an RDS MySQL database in isolated subnets that only the app-tier instances can reach, proving full network segmentation across all three tiers.

In this step, get ready to:
  • Create a security group that only allows database connections from the app tier.
  • Create a DB subnet group to isolate the database in the data-tier subnets.
  • Launch an RDS MySQL database instance in the isolated data tier.
Create the data-tier security group

The data tier needs its own security group to control who can connect to the database. You will use the same security group chaining pattern from Step 4, this time referencing App-SG as the allowed source.

  • Switch back to the VPC console.
  • Click Security Groups in the left sidebar.
  • Click Create security group.
  • Enter Data-SG in the Security group name field.
  • Enter a description such as Security group for data tier.
  • Select three-tier-vpc from the VPC dropdown.
  • Under Inbound rules, click Add rule.
  • Set Type to MYSQL/Aurora.

What does MYSQL/Aurora do?

Selecting MYSQL/Aurora as the type automatically fills in the protocol (TCP) and port (3306). Port 3306 is the standard port for MySQL database connections.

  • In the Source field, search for and select the App-SG security group.
  • Scroll to the bottom and click Create security group.

You should see Data-SG appear in the security groups list with one inbound rule allowing MYSQL/Aurora on port 3306 from App-SG.

Why reference App-SG instead of an IP range?

This is security group chaining. Instead of allowing traffic from a broad IP range like 10.0.0.0/16, you allow traffic only from resources attached to a specific security group. Only your EC2 app servers (which carry App-SG) can connect to the database on port 3306.

If a new app server joins the App-SG group, it automatically gains database access. If an unrelated EC2 instance exists in the same VPC but is not in App-SG, it is blocked. The security boundary follows identity, not network location.

Don't see MYSQL/Aurora in the Type dropdown?

Make sure you are adding an Inbound rule, not an outbound rule. The MYSQL/Aurora type appears in the inbound rules section. If the dropdown still does not show it, select Custom TCP and manually enter 3306 in the Port range field.

Help me create a security group for my data tier with a MYSQL/Aurora inbound rule from App-SG.

Create an RDS subnet group

Before you can launch the database, RDS needs to know which subnets it can deploy into. A DB subnet group is a collection of subnets that you designate for your database instances.

  • Open the Amazon RDS console by searching for RDS in the AWS console search bar.
  • Click Subnet groups in the left navigation pane.
  • Click Create DB Subnet Group.
  • Enter data-subnet-group in the Name field.
  • Enter a description such as Subnet group for data tier.
  • Select three-tier-vpc from the VPC dropdown.

Now add only the two data-tier subnets to this group.

  • In the Add subnets section, select both Availability Zones that contain your data subnets.
  • Under Subnets, select Data-Subnet-AZ1 and Data-Subnet-AZ2. Do not select the app-tier or public subnets.
  • Click Create.

You should see data-subnet-group appear in the subnet groups list with two subnets across two Availability Zones.

What is a DB subnet group?

A DB subnet group tells RDS which subnets are available for your database. RDS picks one of these subnets and assigns the database a private IP address within it.

Each DB subnet group must span at least two Availability Zones. This is an AWS requirement so that if you later enable Multi-AZ deployments, RDS already has a subnet in a second AZ for the standby replica.

Don't see your data subnets in the dropdown?

Make sure you selected three-tier-vpc from the VPC dropdown before choosing subnets. The subnet list only shows subnets belonging to the selected VPC. If you still do not see Data-Subnet-AZ1 or Data-Subnet-AZ2, confirm you renamed them in Step 1.

Help me find and select my data-tier subnets when creating an RDS DB subnet group.

Launch the RDS MySQL instance

With the security group and subnet group ready, you can launch the database itself. The Free tier template auto-selects several settings for you, so the process is straightforward.

  • In the RDS console, click Databases in the left navigation pane.
  • Click Create database.
  • Under Choose a database creation method, confirm Standard create is selected.
  • Under Engine options, select MySQL.
  • Under Templates, select Free tier.
  • In the Settings section, enter three-tier-db in the DB instance identifier field.
  • Set a Master username and Master password you will remember.

What does the Free tier template auto-select?

The Free tier template automatically configures the instance class to db.t3.micro, storage to 20 GB General Purpose SSD (gp2), and disables Multi-AZ deployment. These are exactly the settings you need for this project.

The RDS Free Tier covers 750 hours per month of Single-AZ db.t3.micro instances for 12 months on eligible accounts. Delete the instance when you finish the project to stay within limits.

Now scroll down to the Connectivity section to place the database in your isolated data subnets.

  • Under Compute resource, select Don't connect to an EC2 compute resource.
  • For Virtual Private Cloud (VPC), select three-tier-vpc.
  • For DB subnet group, select data-subnet-group.
  • Set Public access to No.
  • Under VPC security group (firewall), select Choose existing.
  • Select Data-SG and remove the default security group if one is attached.
  • Scroll to the bottom of the page and click Create database.

The database takes 5-10 minutes to create

RDS is provisioning the instance, configuring storage, and setting up networking behind the scenes. You can watch the status change from Creating to Available on the Databases page. Use the refresh icon to check for updates.

Before you check the final result, think about this: which resources in your architecture can connect to this database, and why?

Once the status shows Available, click on three-tier-db to open its details. Under the Connectivity & security tab, confirm that Data-SG is listed under VPC security groups.

How does the full security chain look now?

Your architecture has three layers of security group chaining. The internet can reach ALB-SG on port 80. App-SG only accepts traffic from ALB-SG on port 80. Data-SG only accepts traffic from App-SG on port 3306.

Each tier can only talk to the tier directly above it. A request from the internet cannot skip the ALB and reach the app servers. The app servers cannot be reached except through the ALB. The database cannot be reached except from the app servers. This is least-privilege network segmentation in action.

RDS instance stuck in Creating status?

RDS instances typically take 5-10 minutes to provision. If the status has not changed after 15 minutes, check that your data-subnet-group contains subnets in at least two different Availability Zones. RDS requires subnets in multiple AZs even for Single-AZ deployments.

Help me troubleshoot my RDS MySQL instance that is stuck in Creating status.

Your full 3-tier architecture is now complete. Internet traffic flows through the ALB in the public subnets, to EC2 app servers in the private app subnets, and the database sits in isolated data subnets that only the app tier can reach. Next up, you will put this architecture to the test by simulating an Availability Zone failure.

Test High Availability

Your full 3-tier architecture is live. Internet traffic flows through the ALB to private EC2 app servers, and those servers can reach the RDS database in isolated data subnets.

But you have not proven its most important property: resilience. The whole point of deploying across two Availability Zones is that if one goes down, the other keeps serving traffic. In this step, you will simulate an AZ failure and prove it.

In this step, get ready to:
  • Stop one app-tier instance to simulate an Availability Zone failure.
  • Verify the ALB routes all traffic to the surviving instance.
  • Restart the stopped instance and confirm automatic recovery.
Stop one EC2 instance

To simulate an AZ failure, you will stop one of your two app-tier instances. The ALB runs health checks every 30 seconds, so once it detects the stopped instance, it automatically stops sending traffic to it.

  • In the EC2 console, click Instances in the left sidebar.
  • Select the checkbox next to your instance in App-Subnet-AZ1.
  • Click the Instance state dropdown at the top of the page.
  • Select Stop instance.
  • Click Stop to confirm.

Why stop instead of terminate?

Stopping an instance shuts it down but keeps it on your account so you can restart it later. Terminating deletes it permanently. Since you need to bring this instance back to test recovery, stop is the right choice.

The instance state changes to Stopping and then Stopped. Wait about 30 seconds for the ALB to run its next health check.

Verify failover

The ALB should have detected that one instance is down and started routing all traffic to the surviving instance. Before you check, think about what you expect to see.

What do you think will happen when you refresh the ALB DNS name? Will the page still load, or will it fail since half the app tier is offline?

  • Open a new browser tab.
  • Paste the ALB DNS name into the address bar and press Enter.
  • Refresh the page 5-6 times.

Every response shows the same Availability Zone. All traffic is flowing through the instance in AZ2 because the ALB has automatically routed around the stopped instance.

  • Return to the EC2 console.
  • Click Target Groups in the left sidebar under Load Balancing.
  • Select App-TG.
  • Click the Targets tab at the bottom of the page.

You should see one target listed as healthy and one as unhealthy. The ALB only sends traffic to targets in a healthy state.

What is happening behind the scenes?

The ALB sends an HTTP request to each registered target every 30 seconds (the default health check interval). When a target fails to respond, the ALB marks it as unhealthy after a configurable number of consecutive failures.

Traffic is only sent to targets in a healthy state. This is how a load balancer provides high availability without any manual intervention.

Still seeing both AZs in responses?

The ALB health check runs every 30 seconds. If you refreshed too quickly after stopping the instance, the ALB may not have marked it unhealthy yet. Wait 30-60 seconds and try refreshing again.

Also confirm you stopped the correct instance. Check the Instances page in the EC2 console and verify the instance in App-Subnet-AZ1 shows a Stopped state.

Why is my ALB still routing traffic to both AZs after stopping an instance?

Restart and confirm recovery

You have proven the architecture handles failure. Now prove it recovers automatically when the failed instance comes back online.

  • In the EC2 console, click Instances in the left sidebar.
  • Select the checkbox next to the stopped instance in App-Subnet-AZ1.
  • Click the Instance state dropdown.
  • Select Start instance.

The instance takes 1-2 minutes to fully boot and start the Python HTTP server from its User Data script.

While you wait, think about what you expect. Once the health checks pass, will traffic go back to using both Availability Zones automatically? Or will the ALB stick with the one that was already working?

  • After 1-2 minutes, return to Target Groups and select App-TG.
  • Click the Targets tab.
  • Confirm both targets now show as healthy.
  • Open the ALB DNS name in your browser and refresh 5-6 times.

Traffic distributes across both Availability Zones again. The architecture recovered automatically without any changes to the load balancer.

What you just proved

Your 3-tier architecture survived a simulated AZ failure without any manual intervention on the load balancer. The ALB detected the failed instance, rerouted traffic to the healthy one, and then reintegrated the recovered instance once it passed health checks.

In a real-world scenario, this is what keeps your application online when AWS hardware fails or an entire Availability Zone has issues.

Both targets healthy but still seeing only one AZ?

The User Data script needs time to start the Python HTTP server after the instance boots. Wait another minute and refresh again.

If the issue persists, the User Data script may not have re-executed on restart. The script runs on first launch but does not always re-run on a stop/start cycle. If this happens, the health check will eventually mark the target as unhealthy again because port 80 is not responding.

My restarted EC2 instance is healthy in the Target Group but not serving the expected response. What could be wrong?

Secret mission

Add Auto Scaling to the App Tier

In Step 6 you proved the architecture survives an AZ failure, but you had to manually restart the stopped instance. In production, nobody is sitting at the console pressing Start. Can you replace the manually launched EC2 instances with an Auto Scaling Group that detects a failed instance and automatically launches a replacement?

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. This project uses EC2 instances, an Application Load Balancer, and an RDS database that all consume Free Tier hours while running.

Cost warning

EC2, ALB, and RDS each share a 750-hour monthly Free Tier allowance for eligible accounts (created before July 15, 2025). If your Free Tier period has expired, or your account has other EC2/ALB/RDS usage consuming those hours, charges will apply.

Delete all resources immediately after completing the project to stay within limits. The ALB cannot be paused and continues accruing hours even if you stop your EC2 and RDS instances.

Resources you used:

  • Auto Scaling Group (app-tier-asg) with 2 managed EC2 t2.micro instances.
  • Launch Template (app-tier-lt).
  • Application Load Balancer (three-tier-alb) with Target Group (App-TG).
  • RDS MySQL instance (three-tier-db) with DB Subnet Group (data-subnet-group).
  • Security Groups: ALB-SG, App-SG, and Data-SG.
  • VPC (three-tier-vpc) with 6 subnets, Internet Gateway, and route tables.

Keep everything running

No action needed. Choose this if you are still actively building or plan to experiment further with the architecture.

  • All resources stay within Free Tier limits if your account is eligible and you have no other EC2, ALB, or RDS usage consuming those hours.
  • Monitor your Free Tier usage by navigating to Billing and Cost Management in the AWS Console, then selecting Free Tier from the left sidebar.
  • The Auto Scaling Group continues running 2 instances at all times. These share the 750-hour monthly EC2 Free Tier pool with any other EC2 instances on your account.

Pause - I'll come back to this later

Shut down running compute and database resources to reduce Free Tier consumption while keeping everything in place for later.

  • In the EC2 console, go to Auto Scaling Groups. Select app-tier-asg, click Edit, and set Min capacity and Desired capacity both to 0. This terminates the managed instances while keeping the ASG configuration intact.
  • In the RDS console, select three-tier-db, click Actions, then Stop temporarily. The instance automatically restarts after 7 days, so set a reminder to stop it again or delete it before then.
  • The ALB (three-tier-alb) cannot be stopped or paused. It continues to accrue Free Tier hours while it exists. If you do not plan to return soon, consider deleting it instead.
  • To resume later, set the ASG min and desired capacity back to 2 and start the RDS instance from the RDS console.

Delete - I don't want to use this again

Remove all project resources and start fresh. Delete in this specific order to avoid dependency errors.

  • Delete the Auto Scaling Group first. In the EC2 console, go to Auto Scaling Groups, select app-tier-asg, click Delete, and confirm. This also terminates the 2 managed EC2 instances. Wait until they show as terminated before continuing.
  • Delete the Launch Template. In the EC2 console, go to Launch Templates, select app-tier-lt, click Actions, then Delete template.
  • Delete the ALB. In the EC2 console, go to Load Balancers, select three-tier-alb, click Actions, then Delete load balancer.
  • Delete the Target Group. In the EC2 console, go to Target Groups, select App-TG, click Actions, then Delete.
  • Delete the RDS instance. In the RDS console, select three-tier-db, click Actions, then Delete. Uncheck Create final snapshot if you do not need a backup. This takes a few minutes to complete.
  • Delete the DB Subnet Group. In the RDS console, go to Subnet groups, select data-subnet-group, and click Delete. Wait until the RDS instance deletion finishes before this step, or it will fail.
  • Delete the security groups in this order. In the VPC console, go to Security Groups. Delete Data-SG first, then App-SG, then ALB-SG. This order matters because each security group references the next one in its inbound rules.
  • Delete the VPC last. In the VPC console, go to Your VPCs, select three-tier-vpc, click Actions, then Delete VPC. This automatically deletes all 6 subnets, route tables, and the Internet Gateway.

Nice Work!

Nice Work!

Nice work! You just designed and deployed a complete, highly available 3-tier architecture on AWS.

You've learned how to:

  • Designed a VPC with 6 subnets across 2 Availability Zones, using security group chaining to ensure each tier only accepts traffic from the tier above it.
  • Deployed an internet-facing Application Load Balancer that routes traffic to EC2 instances in private subnets and connected them to an isolated RDS MySQL database, proving full 3-tier network segmentation.
  • Tested high availability by stopping an instance and watching the ALB seamlessly route all traffic to the surviving Availability Zone.
  • Secret Mission: Replaced manual EC2 instances with an Auto Scaling Group for self-healing infrastructure that automatically replaces failed instances.

Ready to quiz yourself?