Build a Multi-Tier HA Architecture
Design a production-grade VPC with load balancing, auto scaling, and RDS.
Introduction
30 Second Summary
Every time you open a banking app, stream a video, or place an online order, you're relying on infrastructure that stays available even when servers fail. Designing systems that survive outages is one of the most in-demand skills in cloud engineering.
In this project, you will build a highly available multi-tier architecture on AWS from scratch. You'll create isolated network layers, deploy self-healing web servers behind a load balancer, and secure a managed database that the internet cannot reach.
What You'll Build
A production-style web application architecture on AWS with a custom VPC, load-balanced compute tier, and a managed database in a private subnet, mirroring the design patterns tested on the SAA-C03 Solutions Architect exam.
By the end of this project, you'll have:
- A custom VPC with public and private subnets spanning 2 Availability Zones, connected by an Internet Gateway and NAT Gateway for controlled access.
- An Application Load Balancer distributing traffic to an Auto Scaling Group of web servers that automatically recovers from instance failures.
- An RDS MySQL database secured in a private subnet with Multi-AZ standby, protected by three layers of defense-in-depth security.
- Secret Mission: Add CloudWatch alarms with SNS notifications and simulate an instance failure to watch your architecture self-heal in real time.
Are there any prerequisites?
You need an active Pluralsight subscription with Cloud Playground access. All AWS resources are created inside the sandbox, so you won't need your own AWS account or credit card.
A basic understanding of networking concepts like subnets, CIDR notation, and routing will help you move through the project faster.
Create a Custom VPC and Subnets
Every production architecture on AWS starts with a VPC (Virtual Private Cloud). A VPC is your own isolated section of the AWS network where you control IP addresses, subnets, and routing.
In this step, you'll create a custom VPC and divide it into 4 subnets spread across 2 Availability Zones. This layout is the foundation of a highly available architecture because if one data center goes down, the other keeps serving traffic.
In this step, get ready to:
- Create a custom VPC with DNS settings enabled.
- Build 4 subnets across 2 Availability Zones.
- Enable auto-assign public IPv4 on your public subnets.
Create a custom VPC with DNS enabled
AWS gives every account a default VPC, but that default doesn't give you the network isolation a multi-tier architecture needs. You'll create a brand new VPC with its own IP address range.
- In the AWS Console search bar at the top, type VPC and select VPC from the results.
- In the left sidebar, click Your VPCs.
- Click Create VPC in the top-right corner.
- Under Resources to create, select VPC only.
- In the Name tag field, enter HA-WebApp-VPC.
- In the IPv4 CIDR block field, enter 10.0.0.0/16.
- Leave all other settings as default and click Create VPC.
What is a CIDR block?
A CIDR block defines the range of IP addresses your VPC can use. The /16 means the first 16 bits are fixed, giving you 65,536 available IP addresses.
A /16 is the largest VPC CIDR block AWS allows. It gives you plenty of room to create subnets for different tiers of your architecture.
Now you need to enable DNS settings. These are critical for RDS database connectivity later in the project.
- On the VPC details page that appears after creation, click Actions in the top-right corner.
- Select Edit VPC settings.
- Under DNS settings, check the box for Enable DNS hostnames.
- Confirm that Enable DNS resolution is also checked (it should be on by default).
- Click Save.
Why enable DNS hostnames?
DNS hostnames let AWS assign readable DNS names to your resources. Without this setting, your RDS database endpoint won't resolve properly when your web servers try to connect to it.
Navigate back to Your VPCs in the left sidebar. You should see HA-WebApp-VPC listed with CIDR 10.0.0.0/16.
Don't see your VPC?
Make sure you're in the US East (N. Virginia) us-east-1 region. Check the region selector in the top-right corner of the console.
If you see an error about CIDR overlap, the sandbox may have a VPC using the same range. Try refreshing the page and checking if the VPC was actually created.
Help me troubleshoot my VPC creation in the AWS Console.
Create 4 subnets across 2 Availability Zones
Subnets divide your VPC into smaller network segments. You'll create 2 public subnets (for resources that need internet access) and 2 private subnets (for resources that should be hidden from the internet).
Each pair spans two Availability Zones (us-east-1a and us-east-1b). If one Availability Zone experiences an outage, the other zone keeps your application running.
- In the left sidebar, click Subnets.
- Click Create subnet.
- In the VPC ID dropdown, select HA-WebApp-VPC.
Now configure the first subnet. You'll add all 4 subnets on this same page before clicking create.
- In the Subnet name field, enter public-subnet-1.
- For Availability Zone, select US East (N. Virginia) / us-east-1a.
- In the IPv4 subnet CIDR block field, enter 10.0.1.0/24.
- Click Add new subnet to add the next subnet on the same page.
Repeat for the remaining 3 subnets using these values:
- public-subnet-2: Availability Zone us-east-1b, CIDR 10.0.2.0/24.
- private-subnet-1: Availability Zone us-east-1a, CIDR 10.0.3.0/24.
- private-subnet-2: Availability Zone us-east-1b, CIDR 10.0.4.0/24.
- After adding all 4 subnets, click Create subnet.
You should see a success banner confirming all 4 subnets were created. They'll appear in the Subnets list filtered to your VPC.
Why spread subnets across 2 Availability Zones?
Each Availability Zone is a physically separate data center (or group of data centers) with independent power, cooling, and networking. Spreading your subnets across us-east-1a and us-east-1b means a failure in one zone won't take down your entire application.
This multi-AZ pattern is the foundation of high availability on AWS. The SAA-C03 exam tests this concept heavily.
Seeing a CIDR overlap error?
Double-check you selected HA-WebApp-VPC in the VPC dropdown at the top of the page, not the default VPC. The default VPC may already use overlapping CIDR ranges.
Help me fix a CIDR overlap error when creating subnets.
Enable auto-assign public IPv4 on public subnets
By default, instances launched into any subnet won't get a public IP address. Your public subnets need this enabled so that resources like your load balancer can be reached from the internet.
- In the Subnets list, select the checkbox next to public-subnet-1.
- Click Actions in the top-right corner.
- Select Edit subnet settings.
- Under Auto-assign IP settings, check the box for Enable auto-assign public IPv4 address.
- Click Save.
- Repeat the same process for public-subnet-2: select it, click Actions, then Edit subnet settings, enable auto-assign public IPv4, and click Save.
To confirm the settings applied, select each public subnet and look at the Details tab below. You should see Auto-assign public IPv4 address set to Yes.
Why only public subnets get auto-assign?
Private subnets are designed for resources that should never be directly reachable from the internet. Your database will live in a private subnet with no public IP and no internet-facing route. This is the first layer of your defense-in-depth security strategy.
Don't see the auto-assign option?
Make sure you selected the subnet's checkbox first (not just clicked on the subnet name). The Actions dropdown only appears when a subnet is checked.
Help me enable auto-assign public IPv4 on my subnet.
Your VPC and subnets are in place. You now have a network foundation with 2 public and 2 private subnets spread across 2 Availability Zones. Next up, you'll wire up internet access by creating an Internet Gateway and NAT Gateway with the right routing rules.
Configure Internet Access and Routing
Your VPC has four subnets, but none of them can reach the internet yet. A VPC is completely isolated by default.
In this step, you'll create the gateways and route tables that control exactly how traffic flows in and out. Public subnets get direct internet access through an Internet Gateway. Private subnets get outbound-only access through a NAT Gateway.
In this step, get ready to:
- Attach an Internet Gateway and route public subnet traffic through it.
- Create a NAT Gateway so private instances can download updates without being exposed to the internet.
- Verify that your routing architecture is correct across all subnets.
Attach an Internet Gateway and create a public route table
An Internet Gateway is the doorway between your VPC and the public internet. Without one, nothing in your VPC can send or receive traffic from outside AWS.
- In the VPC console left sidebar, click Internet gateways.
- Click Create internet gateway.
- Enter a name tag (e.g. HA-WebApp-IGW).
- Click Create internet gateway.
The Internet Gateway is created but in a "Detached" state. You need to attach it to your VPC.
- Click Actions in the top right, then select Attach to VPC.
- Select HA-WebApp-VPC from the dropdown.
- Click Attach internet gateway.
The state should now show "Attached". Next, you need a route table that tells your public subnets to send internet-bound traffic through this gateway.
- In the left sidebar, click Route tables.
- Click Create route table.
- Enter Public-RT as the name.
- Select HA-WebApp-VPC as the VPC.
- Click Create route table.
Now add the internet route to this table.
- With Public-RT selected, click the Routes tab at the bottom of the page.
- Click Edit routes.
- Click Add route.
- For Destination, enter 0.0.0.0/0.
- For Target, select Internet Gateway, then select your Internet Gateway from the list.
- Click Save changes.
Finally, associate this route table with both public subnets so they use it instead of the default main route table.
- Click the Subnet associations tab.
- Click Edit subnet associations.
- Select public-subnet-1 and public-subnet-2.
- Click Save associations.
What is an Internet Gateway?
An Internet Gateway is horizontally scaled, redundant, and highly available by default. It is not a single point of failure in your architecture.
The route 0.0.0.0/0 means "all traffic not destined for the local VPC." By pointing this at the Internet Gateway, any resource in a public subnet can send and receive traffic from the internet.
Create a NAT Gateway for private subnet outbound access
Your private subnets need to download software updates and patches, but they should never be directly reachable from the internet. A NAT Gateway solves this by allowing outbound traffic while blocking inbound connections.
A NAT Gateway requires an Elastic IP address. You'll allocate one first, then create the NAT Gateway.
- In the left sidebar, click Elastic IPs.
- Click Allocate Elastic IP address.
- Leave the default settings (Amazon's pool of IPv4 addresses).
- Click Allocate.
You should see a success banner with your new Elastic IP address. Now create the NAT Gateway.
- In the left sidebar, click NAT gateways.
- Click Create NAT gateway.
- Enter a name (e.g. HA-WebApp-NAT).
- For Subnet, select public-subnet-1.
- For Elastic IP allocation ID, select the Elastic IP you just allocated.
- Click Create NAT gateway.
Why does the NAT Gateway go in a public subnet?
The NAT Gateway needs its own internet access (via the Internet Gateway route in the public subnet) to forward outbound traffic from private instances. Private instances send traffic to the NAT Gateway, which then sends it to the internet on their behalf.
This is a single-AZ NAT Gateway design. On the SAA-C03 exam, you may see questions about this trade-off. A single NAT Gateway is cheaper, but if us-east-1a goes down, private instances in us-east-1b lose outbound internet access. For production, you would place a NAT Gateway in each AZ.
The NAT Gateway takes 1-2 minutes to move from "Pending" to "Available". While it provisions, create the private route table.
- In the left sidebar, click Route tables.
- Click Create route table.
- Enter Private-RT as the name.
- Select HA-WebApp-VPC as the VPC.
- Click Create route table.
Now add the NAT Gateway route.
- With Private-RT selected, click the Routes tab.
- Click Edit routes.
- Click Add route.
- For Destination, enter 0.0.0.0/0.
- For Target, select NAT Gateway, then select your NAT Gateway from the list.
- Click Save changes.
Associate this route table with both private subnets.
- Click the Subnet associations tab.
- Click Edit subnet associations.
- Select private-subnet-1 and private-subnet-2.
- Click Save associations.
What does a NAT Gateway do for private instances?
NAT Gateways allow private instances to reach the internet for updates and patches without being reachable FROM the internet. Outbound connections work. Inbound connections from random IPs are blocked.
Verify the routing architecture
Before moving on, confirm that all three route tables are configured correctly. A misconfigured route table is one of the most common causes of connectivity issues in AWS.
- In the left sidebar, click Route tables.
- Select Public-RT and click the Routes tab.
- Confirm you see two routes: 10.0.0.0/16 (local) and 0.0.0.0/0 targeting the Internet Gateway.
- Click the Subnet associations tab and confirm public-subnet-1 and public-subnet-2 are listed.
- Select Private-RT and click the Routes tab.
- Confirm you see two routes: 10.0.0.0/16 (local) and 0.0.0.0/0 targeting the NAT Gateway.
- Click the Subnet associations tab and confirm private-subnet-1 and private-subnet-2 are listed.
- Find the main route table (it has "Yes" in the Main column).
- Confirm it has only the local route (10.0.0.0/16) and no internet route.
Why check the main route table?
Any subnet you do not explicitly associate with a custom route table uses the main route table by default. By keeping the main route table internet-free, any new subnet you accidentally create stays private. This is a secure-by-default design pattern.
NAT Gateway still showing Pending?
NAT Gateways typically take 1-2 minutes to provision. If it stays in a pending state for longer, confirm that you selected a public subnet (one with the Internet Gateway route) and that the Elastic IP was successfully allocated.
help me troubleshoot why my NAT Gateway is stuck in pending state
Your network plumbing is complete. Traffic can flow in through the Internet Gateway to public subnets, and private subnets can reach the internet for updates without exposing themselves. Next up, you'll build the Security Groups that control exactly which resources can talk to each other.
Build Security Groups
Your network has two distinct zones now: public subnets that can reach the internet, and private subnets that cannot be reached from outside. The next challenge is controlling traffic between the layers of your application.
Security Groups act as virtual firewalls around individual resources. You'll create three of them, each allowing traffic only from the tier directly above it. This pattern is called defense in depth, and it's one of the most heavily tested concepts on the SAA-C03 exam.
In this step, get ready to:
- Create a Security Group for the Application Load Balancer that accepts public HTTP traffic.
- Create a Security Group for the web servers that only accepts traffic from the load balancer.
- Create a Security Group for the database that only accepts traffic from the web servers.
Create the ALB Security Group
The Application Load Balancer is the only resource in your architecture that faces the public internet directly. Its Security Group needs to allow HTTP traffic from anywhere.
- In the left sidebar of the VPC console, click Security groups.
- Click Create security group.
- Enter ALB-SG in the Security group name field.
- Enter Allow HTTP from internet in the Description field.
- In the VPC dropdown, select HA-WebApp-VPC.
Now add the inbound rule that allows public HTTP traffic.
- Under Inbound rules, click Add rule.
- Set Type to HTTP.
- Set Source to Anywhere-IPv4 (0.0.0.0/0).
- Leave the Outbound rules section as default (all traffic allowed).
- Click Create security group.
Why allow traffic from 0.0.0.0/0?
The ALB is your application's front door. It's the only resource that needs to be reachable from the public internet. Every other resource sits behind it, protected by its own Security Group.
On the exam, if a question mentions an internet-facing load balancer, this is the expected pattern. Port 80 open to the world on the ALB, and nothing else directly exposed.
You should see a green success banner confirming that ALB-SG was created.
Don't see your VPC in the dropdown?
Make sure you're in the us-east-1 region. Check the top-right corner of the console. If it shows a different region, click it and switch to US East (N. Virginia).
help me find HA-WebApp-VPC in the Security Group creation form
Create the web tier Security Group
The web servers should only receive traffic that has already passed through the ALB. Instead of allowing an IP range, you'll reference ALB-SG as the source. This is called Security Group chaining.
- Click Security groups in the left sidebar to return to the list.
- Click Create security group.
- Enter WebServer-SG in the Security group name field.
- Enter Allow HTTP from ALB only in the Description field.
- In the VPC dropdown, select HA-WebApp-VPC.
Now add the inbound rule that references ALB-SG as the traffic source.
- Under Inbound rules, click Add rule.
- Set Type to HTTP.
- In the Source field, type ALB-SG and select the Security Group from the dropdown that appears.
- Leave the Outbound rules section as default (all traffic allowed).
- Click Create security group.
What is Security Group chaining?
By referencing another Security Group as the source (instead of an IP range), you create a dynamic rule. Any resource that belongs to ALB-SG is automatically allowed. If the ALB's IP address changes, the rule still works.
This is one of the most heavily tested patterns on the SAA-C03 exam. When you see a question asking how to restrict traffic between tiers, Security Group chaining is almost always the correct answer.
You should see a green success banner confirming that WebServer-SG was created.
Create the database Security Group
The database is your most sensitive resource. It should only accept connections on the MySQL port, and only from the web servers. Combined with its private subnet placement (no internet route) and no public IP, this creates three layers of protection.
- Click Security groups in the left sidebar to return to the list.
- Click Create security group.
- Enter Database-SG in the Security group name field.
- Enter Allow MySQL from web tier only in the Description field.
- In the VPC dropdown, select HA-WebApp-VPC.
Now add the inbound rule for MySQL traffic from the web tier only.
- Under Inbound rules, click Add rule.
- Set Type to MySQL/Aurora. This automatically sets the port to 3306.
- In the Source field, type WebServer-SG and select the Security Group from the dropdown.
- Leave the Outbound rules section as default (all traffic allowed).
- Click Create security group.
What is defense in depth?
Your database is now protected by three independent layers. It lives in a private subnet with no route to the internet. It has no public IP address. Its Security Group only allows port 3306 from WebServer-SG.
An attacker would need to breach all three layers to reach the database. This is the principle of defense in depth, and it's exactly what the SAA-C03 exam means when it asks about "securing a database tier."
You should see a green success banner confirming that Database-SG was created.
Can't find the Security Group in the source dropdown?
When typing a Security Group name in the source field, make sure you've selected HA-WebApp-VPC first. The dropdown only shows Security Groups that belong to the same VPC. Also confirm the earlier Security Groups were created successfully by checking the Security Groups list.
help me reference another Security Group as a source in an inbound rule
Your security architecture is locked down. Traffic can only flow internet → ALB → web servers → database, with each hop controlled by a dedicated Security Group. Next up, you'll launch EC2 instances that use these groups to serve a web page.
Create a Launch Template and Test EC2 Bootstrapping
Your network is locked down with three layers of security groups. Now you need actual compute to serve web traffic.
In this step, you'll create a Launch Template that defines exactly how every web server in your architecture gets configured. The template includes a bootstrap script (called user data) that automatically installs Apache and generates a web page showing instance metadata. This is how real-world architectures achieve consistency across dozens of servers.
In this step, get ready to:
- Create a Launch Template with a bootstrap script.
- Launch a test instance to verify the script works.
- Understand key architecture decisions for the SAA-C03 exam.
Create a Launch Template for the web tier
A Launch Template stores all the configuration your EC2 instances need to launch consistently. You'll configure the AMI, instance type, security group, and a user data script that turns each fresh instance into a working web server.
- In the AWS Console, type EC2 in the top search bar and select **EC2**.
- In the left navigation pane, click Launch Templates under Instances.
- Click Create launch template.
- For Launch template name, enter WebServer-LT.
- Under Application and OS Images (Amazon Machine Image), select Amazon Linux 2023 AMI (it should appear as the default free tier eligible option).
- Under Instance type, select t2.micro.
- Under Key pair (login), select Don't include in launch template. You won't SSH into these instances, which keeps the attack surface minimal.
- Under Network settings, click Select existing security group and choose WebServer-SG from the dropdown.
Now add the bootstrap script that automatically configures each instance on launch.
- Scroll down and expand Advanced details.
- Scroll to the very bottom of the Advanced details section. Find the User data text box.
- Paste the following bootstrap script into the User data field:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
AZ=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/availability-zone)
cat <<EOF > /var/www/html/index.html
<!DOCTYPE html>
<html>
<head>
<title>HA Web App</title>
<style>
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background-color: #f0f8ff; }
.container { background: white; border-radius: 10px; padding: 40px; display: inline-block; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #232f3e; }
.info { color: #527fff; font-size: 1.2em; margin: 10px 0; }
.label { color: #666; font-size: 0.9em; }
</style>
</head>
<body>
<div class="container">
<h1>Highly Available Web Application</h1>
<p class="label">Instance ID:</p>
<p class="info">$INSTANCE_ID</p>
<p class="label">Availability Zone:</p>
<p class="info">$AZ</p>
<p class="label">Status:</p>
<p class="info">Healthy and serving traffic!</p>
</div>
</body>
</html>
EOF
What does this script do?
This script runs automatically the first time each EC2 instance boots. It performs four things in sequence:
- Installs and starts Apache httpd (the web server).
- Retrieves a session token from the Instance Metadata Service v2 (IMDSv2). Amazon Linux 2023 requires this token-based approach for security.
- Fetches the instance's own ID and Availability Zone using the token.
- Generates an HTML page displaying that metadata, so you can visually confirm which instance is serving your request.
- Click Create launch template.
You should see a success banner confirming that WebServer-LT was created.
Don't see the success message?
- Make sure you selected an AMI. If the field says "Don't include in launch template," go back and choose Amazon Linux 2023.
- Double-check that WebServer-SG appears under Network settings. The security group must belong to HA-WebApp-VPC.
- Confirm the User data field is not empty. If you accidentally cleared it, paste the script again.
Help me troubleshoot my Launch Template creation.
Launch a test instance to verify the user data works
Before wiring this template into an Auto Scaling Group, you need to confirm the bootstrap script actually produces a working web page. You'll launch a single instance into a public subnet for a quick visual test.
- On the success page, click Launch instance from template. Alternatively, select WebServer-LT from the Launch Templates list, click Actions, then Launch instance from template.
- Under Network settings, select HA-WebApp-VPC as the VPC.
- For Subnet, choose public-subnet-1 (us-east-1a).
- Under Firewall (security groups), click Select existing security group. Add ALB-SG alongside WebServer-SG. This temporarily allows direct HTTP access from your browser for testing purposes.
- Leave Number of instances set to 1.
- Click Launch instance.
Why add ALB-SG for this test?
Your WebServer-SG only allows HTTP traffic from ALB-SG (not from the open internet). Since you don't have an ALB yet, you need ALB-SG attached directly to the test instance. ALB-SG allows HTTP from 0.0.0.0/0, so your browser can reach the instance. In production, only the ALB will have ALB-SG. This is just for verification.
- Click View all instances.
- Wait for the instance Instance state to show Running and the Status check column to show 2/2 checks passed. This takes 1-2 minutes.
- Select the instance and copy its Public IPv4 address from the details panel below.
- Open a new browser tab and paste the public IP address into the address bar. Press Enter.
You should see a styled page titled "Highly Available Web Application" displaying the instance ID and Availability Zone us-east-1a.
Page not loading?
- Make sure you're using http:// (not https://) in your browser. The web server only listens on port 80.
- Confirm the instance has a public IP. If it doesn't, the subnet may not have auto-assign public IPv4 enabled.
- Verify both ALB-SG and WebServer-SG are attached to the instance by checking the Security tab in the instance details.
- Wait for both status checks to pass before testing. The user data script needs time to install and start Apache.
Help me troubleshoot why my test instance page isn't loading.
Now that you've confirmed the bootstrap works, terminate the test instance.
- Return to the Instances page in the EC2 console.
- Select the test instance.
- Click Instance state in the top menu, then click Terminate instance.
- Click Terminate to confirm.
Understand the architecture decisions
Why Launch Templates instead of Launch Configurations?
Launch Configurations are deprecated by AWS. Launch Templates are the replacement because they support versioning. You can update the AMI or instance type without recreating the entire Auto Scaling Group.
On the SAA-C03 exam, if a question mentions Launch Configurations, the correct answer almost always involves migrating to Launch Templates.
When does user data run?
User data runs only on the first boot of an instance by default. For ongoing configuration management at scale, you'd use AWS Systems Manager. However, user data is perfect for stateless web servers where every instance starts from a clean slate.
This is exactly the pattern the exam tests. Stateless web servers boot, self-configure via user data, and register with a load balancer. No SSH, no manual setup, no configuration drift.
Your Launch Template is ready and proven. Next up, you'll create an Application Load Balancer that distributes incoming traffic across multiple instances in different Availability Zones.
Create an Application Load Balancer
Your Launch Template is ready and you've confirmed that Apache serves the web page correctly. The next challenge is giving users a single, reliable URL to reach your application instead of individual instance IPs.
An Application Load Balancer solves this by sitting in front of your web servers and distributing incoming traffic across multiple instances in different Availability Zones. If one instance goes down, the ALB stops sending traffic to it automatically.
In this step, get ready to:
- Create a Target Group that defines how the ALB health-checks your instances.
- Create an internet-facing Application Load Balancer spanning both public subnets.
- Note the ALB DNS name that will serve as your single traffic entry point.
Create a Target Group
Before creating the load balancer itself, you need a Target Group. This tells the ALB which instances to send traffic to and how to check whether they're healthy enough to receive requests.
- In the left sidebar of the EC2 console, scroll down to the Load Balancing section.
- Click Target Groups.
- Click Create target group.
- For target type, select Instances.
- Enter WebServer-TG as the Target group name.
- Set Protocol to HTTP and Port to 80.
- Under VPC, select HA-WebApp-VPC.
- Expand the Health checks section.
- Set Health check path to /.
- Click Advanced health check settings to expand it.
- Set Healthy threshold to 2.
- Set Unhealthy threshold to 3.
- Set the Interval to 30 seconds.
- Click Next.
- On the Register targets page, do NOT select any instances. Auto Scaling will register instances automatically in the next step.
- Click Create target group.
What is a Target Group?
A Target Group is a collection of resources (in this case, EC2 instances) that receive traffic from a load balancer. The ALB uses health checks to confirm each target can handle requests before sending traffic to it.
The healthy threshold of 2 means an instance must pass 2 consecutive checks before receiving traffic. The unhealthy threshold of 3 means it must fail 3 checks before being removed. This prevents a single blip from removing a healthy instance.
You should see WebServer-TG appear in the Target Groups list with a status of No targets.
Create the Application Load Balancer
Now you'll create the ALB itself. This is the internet-facing component that accepts all incoming HTTP requests and forwards them to the instances registered in your Target Group.
- In the left sidebar, click Load Balancers (still under the Load Balancing section).
- Click Create load balancer.
- Under Application Load Balancer, click Create.
- Enter WebApp-ALB as the Load balancer name.
- For Scheme, select Internet-facing.
- For IP address type, select IPv4.
- Under Network mapping, select HA-WebApp-VPC from the VPC dropdown.
- Check the box for us-east-1a and select public-subnet-1.
- Check the box for us-east-1b and select public-subnet-2.
Why does the ALB need both public subnets?
ALBs must be deployed in at least 2 Availability Zones. If one AZ experiences an outage, the ALB nodes in the other AZ continue serving traffic. This is a core high availability pattern tested on the SAA-C03.
- Under Security groups, remove the default security group by clicking the X next to it.
- Select ALB-SG from the dropdown.
- Under Listeners and routing, confirm the listener is set to HTTP on port 80.
- For Default action, select WebServer-TG as the target group to forward to.
- Scroll down and click Create load balancer.
What makes this a Layer 7 load balancer?
Application Load Balancers operate at the HTTP/HTTPS layer (Layer 7 of the OSI model). This means they can inspect request content and make routing decisions based on URL paths or hostnames.
For example, you could route /api/* to one target group and /images/* to another. This is the right choice when you need path-based or host-based routing.
You should see a success banner confirming your load balancer was created.
Don't see your load balancer?
If you get a validation error about subnets, make sure you selected both public subnets (not private). The ALB needs subnets with an internet route.
If the security group dropdown doesn't show ALB-SG, confirm you selected HA-WebApp-VPC in the Network mapping section first. Security groups are VPC-specific.
Help me troubleshoot my ALB creation.
Note the ALB DNS name
The ALB needs a moment to provision its nodes across both Availability Zones. Once it's active, you'll grab the DNS name that serves as the single entry point for all user traffic.
- Click View load balancer (or click WebApp-ALB in the Load Balancers list).
- Wait for the State to change from Provisioning to Active. This typically takes 1-3 minutes.
- In the Description tab, find the DNS name field.
- Copy this DNS name and save it somewhere accessible. You'll use it to test your architecture once instances are registered.
Why a DNS name instead of an IP address?
ALBs scale automatically behind the scenes by adding or removing nodes. The DNS name resolves to multiple IP addresses across AZs, and those IPs can change as the ALB scales.
This is why you should never hardcode an ALB's IP address. Always use the DNS name. The SAA-C03 tests this concept when asking about DNS resolution and CNAME records.
Don't try the DNS name yet
If you open the ALB DNS name in your browser right now, you'll get a 503 error. That's expected. The Target Group has no registered instances yet. Auto Scaling will handle that in the next step.
Your load balancer is now ready to distribute traffic. In the next step, you'll create an Auto Scaling Group that launches instances into private subnets and registers them with this ALB automatically.
Configure Auto Scaling for Self-Healing
Your Application Load Balancer is ready to distribute traffic, but it has nowhere to send requests yet. You need instances behind it that can serve your web application across both Availability Zones.
Instead of launching instances manually, you'll use an Auto Scaling Group to manage your web tier. Auto Scaling automatically maintains your desired number of instances, launches replacements when one fails, and scales out when demand increases. This is the foundation of self-healing architecture on AWS.
In this step, get ready to:
- Create an Auto Scaling Group that places web servers in private subnets.
- Configure scaling policies and health checks for self-healing.
- Verify instances launch, register with the ALB, and serve traffic.
Create an Auto Scaling Group
The Auto Scaling Group ties together your Launch Template, your VPC subnets, and your Target Group into a single self-managing fleet of instances.
- In the left sidebar of the EC2 console, scroll down to the Auto Scaling section.
- Click Auto Scaling Groups.
- Click Create Auto Scaling group.
- Enter WebServer-ASG as the Auto Scaling group name.
- In the Launch template dropdown, select WebServer-LT.
- Click Next.
On the network settings page, you'll choose where your instances launch. This is a critical security decision.
- In the VPC dropdown, select HA-WebApp-VPC.
- In the Availability Zones and subnets section, select private-subnet-1 (us-east-1a) and private-subnet-2 (us-east-1b).
- Click Next.
Why private subnets for web servers?
Your web servers sit in private subnets with no direct internet access. The ALB (in public subnets) receives internet traffic and forwards it to the private instances.
This is a key secure architecture pattern on the SAA-C03 exam. The only path from the internet to your web servers is through the load balancer, which enforces health checks and distributes traffic evenly.
Configure group size and scaling policies
Now you'll connect the Auto Scaling Group to your load balancer and define how it should respond to failures and demand changes.
- On the load balancing page, select Attach to an existing load balancer.
- Select Choose from your load balancer target groups.
- In the dropdown, select WebServer-TG.
Next, configure the health check settings. This determines how Auto Scaling detects failures.
- Under Health checks, check the box to Turn on Elastic Load Balancing health checks.
- Set the Health check grace period to 120 seconds.
- Click Next.
Why ELB health checks instead of EC2 checks?
EC2 status checks only confirm the instance's operating system is running. ELB health checks go further by verifying that your application is actually responding to HTTP requests.
If your app crashes but the OS keeps running, only ELB health checks will catch it. Auto Scaling then terminates the unhealthy instance and launches a replacement. The exam tests this distinction frequently.
On the group size and scaling page, define how many instances you want running and when to scale.
- Set Desired capacity to 2.
- Set Min desired capacity to 2.
- Set Max desired capacity to 4.
Now configure the scaling policy so your group can grow automatically under load.
- Under Automatic scaling, select Target tracking scaling policy.
- Set the Metric type to Average CPU utilization.
- Set the Target value to 50.
- Click Next.
What does target tracking at 50% mean?
Target tracking works like a thermostat. You set the target (50% CPU), and Auto Scaling adds or removes instances to keep the average at that level.
If average CPU across your instances exceeds 50%, Auto Scaling launches new instances (up to your maximum of 4). When load drops, it terminates extra instances (down to your minimum of 2). This keeps costs optimized during low traffic.
- Skip the notifications page by clicking Next.
- Skip the tags page by clicking Next.
- Review your settings, then click Create Auto Scaling group.
Auto Scaling Group not creating?
Make sure you selected both private subnets in the network step. Auto Scaling requires at least one subnet to launch instances into.
If the Launch Template dropdown is empty, confirm you created WebServer-LT in a previous step and that you're still in the us-east-1 region.
Help me troubleshoot my Auto Scaling Group creation.
Verify instances launch and register with the ALB
Auto Scaling is now launching 2 instances in your private subnets. It takes a couple of minutes for them to boot, run the user data script, and register as healthy with the Target Group.
- On the WebServer-ASG details page, click the Activity tab.
- Wait until you see 2 successful launch activities listed.
Next, confirm the instances are healthy in the Target Group.
- In the left sidebar, click Target Groups under Load Balancing.
- Click WebServer-TG.
- Click the Targets tab.
- Wait for both instances to show a status of healthy. This may take 1-2 minutes.
What if targets show 'initial' or 'unhealthy'?
The health check grace period is 120 seconds, so Auto Scaling gives instances time to boot and start Apache before checking their health. An 'initial' status means the instance is still within this grace period.
If a target stays 'unhealthy' after a few minutes, the user data script may have failed. Check that WebServer-SG allows inbound traffic on port 80 from ALB-SG.
Finally, test the ALB to confirm it's distributing traffic across both instances.
- In the left sidebar, click Load Balancers.
- Select WebApp-ALB and copy its DNS name from the Description section.
- Paste the DNS name into a new browser tab.
- Refresh the page multiple times. You should see the Instance ID and Availability Zone change between refreshes as the ALB distributes traffic across both instances.
What should I see?
You should see a page titled "Highly Available Web Application" with an Instance ID and Availability Zone displayed. On each refresh, the Instance ID alternates between two values and the AZ switches between us-east-1a and us-east-1b.
This proves your ALB is distributing traffic across both Availability Zones. If one AZ goes down, the other keeps serving traffic with no manual intervention.
Page not loading or showing an error?
If you see a 502 or 503 error, the instances may still be initializing. Wait another minute and try again.
If the page never loads, verify that ALB-SG allows inbound HTTP on port 80 from 0.0.0.0/0 and that WebServer-SG allows inbound HTTP on port 80 from ALB-SG.
Help me debug why my ALB isn't serving traffic from my Auto Scaling instances.
Your architecture now has a self-healing web tier. If an instance fails, Auto Scaling detects it through ELB health checks and launches a replacement automatically. Next up, you'll add the final tier by deploying an RDS database in the private subnets to complete the multi-tier architecture.
Deploy RDS in a Private Subnet
Your compute tier is fully operational. The Auto Scaling Group keeps two healthy instances running, and the Application Load Balancer distributes traffic across both Availability Zones.
Now it's time to add the data tier. You'll deploy a managed RDS MySQL database into your private subnets with Multi-AZ enabled for automatic failover. By the end of this step, your database will be triple-isolated from the internet: no public IP, no internet route, and a Security Group that only allows connections from your web servers.
In this step, get ready to:
- Create a DB Subnet Group that tells RDS which private subnets to use.
- Launch an RDS MySQL instance with Multi-AZ for automatic failover.
- Verify the database is completely unreachable from the internet.
Create a DB Subnet Group
Before you can launch a database, RDS needs to know which subnets it's allowed to place instances in. A DB Subnet Group is a collection of subnets that RDS uses for this purpose. For Multi-AZ deployments, you need subnets in at least 2 Availability Zones.
- In the AWS Console search bar at the top, type RDS and select the RDS service.
- In the left sidebar, click Subnet groups.
- Click Create DB subnet group.
- Enter webapp-db-subnet-group for the Name field.
- Enter a description such as Private subnets for RDS.
- In the VPC dropdown, select HA-WebApp-VPC.
- In the Availability Zones section, select us-east-1a and us-east-1b.
- In the Subnets section, select the subnet with CIDR 10.0.3.0/24 (private-subnet-1) and the subnet with CIDR 10.0.4.0/24 (private-subnet-2).
- Click Create.
You should see a success banner confirming the subnet group was created.
What is a DB Subnet Group?
A DB Subnet Group tells RDS which subnets it can place database instances in. For a Multi-AZ deployment, RDS places the primary instance in one subnet and the standby replica in another.
By choosing only private subnets, you ensure the database never gets a public IP or a route to the internet. This is the first layer of database isolation.
Don't see your VPC in the dropdown?
Make sure 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.
Also confirm you enabled DNS hostnames on your VPC back in Step 1. RDS requires DNS hostnames to generate the database endpoint.
Help me troubleshoot my DB Subnet Group creation.
Launch an RDS MySQL instance with Multi-AZ
With the subnet group in place, you can now launch the database itself. Multi-AZ creates a synchronous standby replica in a different Availability Zone. If the primary fails, RDS automatically promotes the standby with no manual intervention required.
- In the left sidebar, click Databases.
- Click Create database.
- Select Standard create for the creation method.
- Under Engine options, select MySQL.
- Under Templates, select Dev/Test. This unlocks the Multi-AZ option.
- Under Availability and durability, select Multi-AZ DB instance.
- Under Settings, enter a DB instance identifier of your choice (for example, webapp-db).
- Set a Master username (for example, admin).
- Set a Master password and confirm it. Choose something memorable for this sandbox session.
- Under Instance configuration, select Burstable classes and choose db.t3.micro.
- Under Storage, set Allocated storage to 20 GB. Storage type should be General Purpose SSD (gp2).
- Under Connectivity, select HA-WebApp-VPC for the VPC.
- For DB subnet group, select webapp-db-subnet-group.
- For Public access, select No.
- Under VPC security group, select Choose existing. Remove the default security group and select Database-SG.
- Scroll to the bottom and click Create database.
Why Multi-AZ instead of Read Replicas?
Multi-AZ is for high availability. It creates a synchronous standby in a different AZ that automatically takes over if the primary fails. You get zero data loss and minimal downtime during failover.
Read Replicas are for read performance. They use asynchronous replication and are used to offload read queries from the primary. On the SAA-C03 exam, if a question asks about failover or availability, the answer is Multi-AZ. If it asks about scaling reads, the answer is Read Replicas.
RDS takes 10-20 minutes to provision
Multi-AZ deployments take longer because RDS sets up both the primary and the standby. You can watch the status change from Creating to Available on the Databases page. Use this time to review the architecture you've built so far.
Verify database isolation
Once the RDS instance shows Available status, you can confirm the three layers of isolation protecting your database. This defense-in-depth pattern is heavily tested on the SAA-C03 exam.
- On the Databases page, click on your database identifier to open its details.
- In the Connectivity & security tab, locate the Endpoint value. Note this down for reference.
- Confirm that Publicly accessible shows No.
- Confirm that the VPC security groups section shows Database-SG.
Why is this database unreachable from the internet?
Your database has three independent layers of protection working together. First, it sits in a private subnet with no route to an Internet Gateway. Second, its Security Group only allows port 3306 traffic from WebServer-SG. Third, public access is explicitly set to No, so no public IP is assigned.
Even if one layer were somehow misconfigured, the other two would still block unauthorized access. This is defense in depth, and it's a core principle tested across multiple SAA-C03 domains.
RDS stuck in Creating status?
Multi-AZ deployments can take up to 20 minutes. This is normal. Make sure you selected db.t3.micro as the instance class, since the Pluralsight sandbox restricts instance types.
If the status changes to an error state, check that your DB Subnet Group contains subnets in two different Availability Zones. RDS requires this for Multi-AZ.
Help me troubleshoot my RDS instance creation.
Secret mission
CloudWatch Monitoring and Failure Simulation
Prove your architecture self-heals by simulating a failure and watching Auto Scaling respond. You'll set up CloudWatch alarms with SNS notifications, then deliberately break something to watch it fix itself.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely inside the Pluralsight Cloud Playground sandbox. All resources are automatically deleted when your session expires after four hours, and you will not be charged.
Resources you used:
- 1 VPC (HA-WebApp-VPC) with 4 subnets across 2 Availability Zones.
- 1 Internet Gateway and 1 NAT Gateway (with Elastic IP).
- 2 Route Tables (Public-RT, Private-RT).
- 3 Security Groups (ALB-SG, WebServer-SG, Database-SG).
- 1 Launch Template (WebServer-LT) and 1 Auto Scaling Group (WebServer-ASG) with 2 running EC2 instances.
- 1 Application Load Balancer (WebApp-ALB) and 1 Target Group (WebServer-TG).
- 1 RDS MySQL instance (Multi-AZ) with DB Subnet Group.
- 1 CloudWatch Alarm and 1 SNS Topic (WebApp-Alerts) from the Secret Mission.
Keep everything running
No action needed. The Pluralsight Cloud Playground automatically shuts down your sandbox and deletes all resources after four hours.
Choose this if you're still actively exploring the architecture or want to experiment with additional configurations before your session expires.
Pause - I'll come back to this later
Pausing is not applicable for the Cloud Playground. Your sandbox session cannot be paused or extended beyond the four-hour window.
If your session expires before you finish, start a new sandbox session and rebuild the architecture from Step 1. The rebuild goes much faster the second time.
Delete - I don't want to use this again
Your sandbox auto-deletes everything when it expires. However, practicing the manual deletion order is useful exam knowledge. You cannot delete a VPC until all dependent resources are removed first.
Why does deletion order matter?
AWS enforces dependency constraints. A VPC cannot be deleted while subnets, gateways, or ENIs still reference it. Understanding this dependency chain is tested on the SAA-C03 exam in scenarios about decommissioning workloads.
Delete resources in this order:
- Navigate to CloudWatch and delete your CPU Utilization alarm. Then navigate to SNS and delete the WebApp-Alerts topic.
- Navigate to EC2 > Auto Scaling Groups and delete WebServer-ASG. This terminates its running instances automatically.
- Navigate to EC2 > Load Balancers and delete WebApp-ALB. Then navigate to Target Groups and delete WebServer-TG.
- Navigate to RDS > Databases and delete the MySQL instance. Uncheck Create final snapshot and check I acknowledge that upon instance deletion, automated backups will no longer be available. Then delete the webapp-db-subnet-group from Subnet Groups.
- Navigate to VPC > NAT Gateways and delete the NAT Gateway. Wait for its state to show Deleted, then navigate to Elastic IPs and release the Elastic IP address.
- Navigate to VPC > Internet Gateways. Select the Internet Gateway, click Detach from VPC, then delete it.
- Delete all three Security Groups (ALB-SG, WebServer-SG, Database-SG) from VPC > Security Groups.
- Delete all 4 subnets from VPC > Subnets.
- Delete Public-RT and Private-RT from VPC > Route Tables. Leave the main route table (it deletes with the VPC).
- Finally, navigate to VPC > Your VPCs and delete HA-WebApp-VPC.
Nice Work!
Nice Work!
You just built a production-grade, highly available architecture on AWS from scratch. This is the same multi-tier pattern that Solutions Architects design for real-world applications every day.
Here's what you accomplished:
- Designed a secure, isolated network. Custom VPC with public and private subnets across two Availability Zones, Security Group chaining for defense in depth, and controlled internet access through an Internet Gateway and NAT Gateway.
- Built a resilient, self-healing web tier. An Application Load Balancer distributing traffic across AZs, with an Auto Scaling Group that maintains desired capacity and automatically replaces failed instances.
- Deployed a managed Multi-AZ database. RDS MySQL in private subnets with automatic failover, completely unreachable from the internet thanks to three layers of protection.
- Proved self-healing works. Simulated an instance failure, watched Auto Scaling launch a replacement within minutes, and confirmed the ALB continued serving traffic without any downtime.
This architecture maps directly to SAA-C03 exam questions. When you see scenarios about highly available web applications or minimizing single points of failure, you have now built the answer.
Ready to quiz yourself?