Build a Highly Available AWS Platform
Deploy a self-healing multi-tier web platform with load balancing and auto scaling.
Introduction
30 Second Summary
Ever tried to visit a website during a big sale or product launch, only to find it completely down? Behind every site that stays up under pressure is an architecture designed to survive failures and handle sudden traffic spikes.
In this project, you will build a highly available web application platform on AWS that automatically recovers when a server goes down. You will design a secure multi-tier network, deploy self-healing servers behind a load balancer, and prove the whole system works by breaking it.
What You'll Build
When you are done, you will be able to pull the plug on a running server, refresh your browser, and watch the system heal itself in real time.
By the end of this project, you'll have:
- A VPC spanning two Availability Zones with public and private subnets, keeping your application servers hidden from direct internet access while NAT gateways give them a safe path to the outside world.
- An Application Load Balancer and Auto Scaling Group working together so you can terminate a server, refresh your browser, and watch a replacement spin up automatically with zero downtime.
- Secure admin access through Session Manager with no SSH ports exposed to the internet. You connect to private servers right from your browser without managing SSH keys.
- Secret Mission: Add a target tracking scaling policy that automatically scales your platform out when CPU utilization exceeds a threshold.
Are there any prerequisites?
You need an AWS account with admin-level access. No local installations are required because everything is done through the AWS Management Console.
Before We Start
Before we dive in, let's lock in what you're about to build and why each piece of the architecture matters.
Build the Production Network
Your web server is live, but it sits in the default VPC with no network isolation. Public and private resources share the same flat network. If you added a database, it would be just as exposed as your web server.
In this step, you will build a dedicated VPC with separate public and private tiers across two Availability Zones. Public subnets will face the internet. Private subnets will stay hidden behind NAT gateways. This network isolation is the foundation everything else builds on.
In this step, get ready to:
- Create a multi-AZ VPC with public and private subnets using the VPC wizard.
- Verify the network topology by tracing traffic flow in the resource map.
- Create least-privilege security groups for the load balancer and application tiers.
Create the ha-platform VPC
AWS provides a VPC wizard called VPC and more that creates your entire network in one step. It sets up subnets, route tables, NAT gateways, and an internet gateway together.
- In the AWS Console search bar, type VPC and select VPC to open the VPC console.
- Click Create VPC.
- For Resources to create, select VPC and more.
What is a VPC?
A VPC (Virtual Private Cloud) is your own isolated section of the AWS network. You control the IP address ranges, subnets, route tables, and gateways inside it.
The default VPC that every AWS account comes with is a flat, open network. Building a dedicated VPC lets you separate public-facing components from private application and data tiers.
- For Name tag auto-generation, enter ha-platform.
- Leave the IPv4 CIDR block at the default value.
- Set Number of Availability Zones to 2.
- Set Number of public subnets to 2.
- Set Number of private subnets to 2.
- Set NAT gateways to 1 per AZ.
- Leave the VPC endpoints section at the default (S3 Gateway).
- Click Create VPC.
Why one NAT gateway per AZ?
A NAT gateway lets private subnets reach the internet for outbound traffic (like downloading software updates) without being reachable from the internet. Placing one in each Availability Zone means a failure in one AZ does not cut off outbound access for the other.
Using a single shared NAT gateway would save a few cents per hour but create a single point of failure. AWS recommends one per AZ for production workloads.
NAT gateways cost money while running
Each NAT gateway costs $0.045 per hour. With two running, that is about $0.09 per hour ($2.16 per day). You will tear these down in the Before You Go section to stop the charges.
The wizard takes about a minute to create all the resources. Wait until the status shows all resources created successfully.
Examine the VPC resource map
Before you move on, take a moment to understand what the wizard just built. The VPC resource map shows every component and how traffic flows between them.
- In the left sidebar, click Your VPCs.
- Select the ha-platform VPC by clicking its checkbox.
- Click the Resource map tab.
Before you trace the routes, think about this: can traffic from the internet reach instances in the private subnets directly?
What should I see?
You should see 4 subnets split across 2 Availability Zones. Two are public subnets and two are private subnets.
Each public subnet has a route table pointing 0.0.0.0/0 to the internet gateway. Traffic from these subnets can reach the internet directly, and the internet can reach them.
Each private subnet has a route table pointing 0.0.0.0/0 to its AZ's NAT gateway. Outbound traffic goes through the NAT gateway, but nothing from the internet can initiate a connection into these subnets. That is the isolation boundary.
The answer is no. Private subnets only route outbound through the NAT gateway. There is no route from the internet gateway into them. This is the network isolation you just created.
Create the security groups
Security groups act as firewalls for your resources. You need two: one for the Application Load Balancer that faces the internet, and one for the application servers that only accept traffic from the load balancer.
- In the left sidebar of the VPC console, click Security groups.
- Click Create security group.
- For Security group name, enter alb-sg.
- For Description, enter Allow HTTP from internet.
- For VPC, remove the default VPC and select the ha-platform VPC.
- 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 the alb-sg security group detail page. Notice there is no SSH rule. The load balancer only needs HTTP traffic. Now create the app server security group.
- In the left sidebar, click Security groups to return to the list.
- Click Create security group.
- For Security group name, enter app-sg.
- For Description, enter Allow HTTP from ALB only.
- For VPC, remove the default VPC and select the ha-platform VPC.
- Under Inbound rules, click Add rule.
- Set Type to HTTP.
- For Source, select Custom, then type alb-sg in the search field and select the alb-sg security group from the dropdown.
- Click Create security group.
Why reference a security group as the source?
Instead of allowing traffic from a specific IP range, you reference the alb-sg security group itself. This means only resources attached to alb-sg can send traffic to your app servers. If the load balancer's IP changes, the rule still works.
Also notice there is no SSH rule on app-sg. In Step 1, your fragile instance had SSH open to the entire internet. These app servers have zero inbound access from the public internet. You will use Session Manager to connect to them securely in a later step.
Now verify both security groups are configured correctly.
- Click Security groups in the left sidebar.
- In the search bar, type ha-platform to filter by your VPC.
- Click on alb-sg and check its Inbound rules tab. You should see one rule: HTTP on port 80 from 0.0.0.0/0.
- Go back and click on app-sg. Check its Inbound rules tab. You should see one rule: HTTP on port 80 with the source set to alb-sg.
Don't see the right rules?
- Make sure you selected the ha-platform VPC when creating each security group. Security groups are VPC-specific.
- For app-sg, confirm the source is the alb-sg security group, not an IP address. You cannot change the source type after creation. If it is wrong, delete the security group and recreate it.
- Help me check that my VPC security groups are configured correctly.
Your production network is in place with clear isolation between public and private tiers. Next up, you will deploy your application behind a load balancer into those private subnets.
Deploy the Application Behind a Load Balancer
Your network is ready. The ha-platform VPC has public and private tiers across two Availability Zones, with security groups enforcing traffic rules.
Right now, a single EC2 instance serves all traffic. If that instance fails, the entire application goes down.
An Application Load Balancer distributes incoming traffic across multiple instances so no single failure takes the application offline. Combined with an Auto Scaling Group that automatically replaces failed instances, your platform stays up even when things go wrong.
In this step, get ready to:
- Create an IAM role and launch template for your application instances.
- Deploy an Application Load Balancer with a target group to distribute traffic.
- Configure an Auto Scaling Group to maintain and replace instances automatically.
Set up the IAM role and launch template
The launch template defines the blueprint for every instance the Auto Scaling Group creates. Before you build the template, you need an IAM role that grants instances the permissions they need for secure admin access later.
- Navigate to IAM by typing IAM in the AWS Console search bar at the top and selecting IAM.
- Click Roles in the left sidebar.
- Click Create role.
- Under Trusted entity type, select AWS service.
- Under Use case, select EC2.
- Click Next.
Now attach the permissions policy that enables secure shell access.
- Type AmazonSSMManagedInstanceCore in the permissions search bar.
- Select the checkbox next to AmazonSSMManagedInstanceCore.
- Click Next.
- Enter ssm-instance-role in the Role name field.
- Click Create role.
A success banner confirms the role was created.
What does this IAM role enable?
The AmazonSSMManagedInstanceCore policy lets Session Manager connect to your instances through the AWS Console. You will use this in the next step to get a terminal on instances that have no public IP and no SSH port open.
By selecting EC2 as the trusted entity, you allowed EC2 instances to assume this role and use its permissions.
Now create the launch template that the Auto Scaling Group will use to spin up instances.
- Navigate to EC2 by typing EC2 in the AWS Console search bar and selecting EC2.
- Click Launch Templates under Instances in the left sidebar.
- Click Create launch template.
- Enter ha-platform-template in the Launch template name field.
- Under Application and OS Images (Amazon Machine Image), click Quick Start and select the Amazon Linux 2023 AMI.
- Under Instance type, select t2.micro.
- Under Key pair (login), select Don't include in launch template.
- Under Network settings, click Select existing security group.
- Select app-sg from the security groups dropdown.
- Scroll down and expand the Advanced details section.
- Under IAM instance profile, select ssm-instance-role.
- Scroll to the bottom and find the User data field. Paste the following script:
#!/bin/bash
# Update packages and install Apache web server
yum update -y
yum install -y httpd
# Fetch instance metadata using IMDSv2
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)
# Create a web page displaying instance identity
cat <<EOF > /var/www/html/index.html
<html>
<body>
<h1>Hello from $INSTANCE_ID</h1>
<p>Availability Zone: $AZ</p>
</body>
</html>
EOF
# Start Apache and enable it on boot
systemctl start httpd
systemctl enable httpd
What does this script do?
- It installs and starts the Apache web server (httpd).
- It fetches the instance's unique ID and Availability Zone from the EC2 metadata service using IMDSv2.
- It creates an HTML page displaying that identity. When you load balance across multiple instances, the changing ID and AZ prove traffic is being distributed.
- Click Create launch template.
A success page confirms the launch template was created.
Launch template creation failed?
- Make sure you selected the Amazon Linux 2023 AMI, not Amazon Linux 2.
- Verify you picked app-sg from the ha-platform VPC, not a security group from the default VPC.
- If ssm-instance-role does not appear in the IAM instance profile dropdown, scroll back up to the IAM role steps and confirm the role was created for the EC2 use case.
Help me troubleshoot my launch template creation.
Create the Application Load Balancer
The ALB sits in the public subnets and becomes the single entry point for all HTTP traffic. It needs a target group that defines where to send requests and how to check instance health.
Start by creating the target group.
- In the EC2 console left sidebar, click Target Groups under Load Balancing.
- Click Create target group.
- Select Instances as the target type.
- Enter ha-platform-tg in the Target group name field.
- Select ha-platform as the VPC.
- Leave the protocol (HTTP), port (80), and health check path (/) at their defaults.
- Click Next.
- Click Create target group without registering any targets.
Why skip target registration?
The Auto Scaling Group handles target registration automatically. When the ASG launches an instance, it registers it with the target group. When an instance is terminated, it deregisters it. You never need to manage targets by hand.
Now create the load balancer itself.
- Click Load Balancers in the left sidebar under Load Balancing.
- Click Create load balancer.
- Under Application Load Balancer, click Create.
- Enter ha-platform-alb in the Load balancer name field.
- Confirm Internet-facing is selected under Scheme.
- Under Network mapping, select the ha-platform VPC.
- Select both Availability Zones and choose the public subnet in each.
- Under Security groups, remove the default security group.
- Select alb-sg as the security group.
- Under Listeners and routing, keep Protocol HTTP and Port 80.
- Under Default action, select ha-platform-tg as the target group.
- Click Create load balancer.
The ALB starts in Provisioning state. Wait about 2-3 minutes for it to change to Active.
- Once active, click on ha-platform-alb to open its details.
- Copy the DNS name shown in the description panel. You will use this to test traffic distribution shortly.
ALB stuck in provisioning?
- ALBs typically take 2-3 minutes to become active. Refresh the page after waiting.
- Make sure you selected the two public subnets from ha-platform, not the private subnets. The ALB must sit in public subnets with internet gateway access.
Help me troubleshoot my ALB.
Launch the Auto Scaling Group and verify traffic
The Auto Scaling Group uses your launch template to maintain the desired number of instances across both private subnets. If an instance fails a health check, the ASG automatically terminates it and launches a replacement.
- In the left sidebar, click Auto Scaling Groups under Auto Scaling.
- Click Create Auto Scaling group.
- Enter ha-platform-asg in the Auto Scaling group name field.
- Under Launch template, select ha-platform-template.
- Click Next.
- Under Network, select the ha-platform VPC.
- Under Availability Zones and subnets, select both private subnets.
- Click Next.
Now configure the load balancer attachment and health checks.
- Under Load balancing, select Attach to an existing load balancer.
- Select Choose from your load balancer target groups.
- Select ha-platform-tg from the list.
- Under Health checks > Additional health check types, turn on Elastic Load Balancing health checks.
- Set the Health check grace period to 120 seconds.
- Click Next.
Why 120 seconds for the grace period?
When an instance first launches, it needs time to boot, run the user data script, and start the web server. The grace period tells the ASG to wait 120 seconds before evaluating health checks. Without it, the ASG might terminate a perfectly healthy instance that simply had not finished starting up yet.
- Under Group size, set Desired capacity to 2.
- Under Scaling > Scaling limits, set Min desired capacity to 2.
- Set Max desired capacity to 4.
- Click Skip to review.
- Click Create Auto Scaling group.
The ASG immediately starts launching two instances in the private subnets. Wait 2-3 minutes for them to boot and pass their health checks.
- Click on ha-platform-asg to open its details.
- Click the Instance management tab to see the two instances launching.
- In the left sidebar, click Target Groups under Load Balancing.
- Click ha-platform-tg and select the Targets tab.
- Wait until both registered targets show a healthy status.
Before you test the load balancer, predict what you will see when you refresh the page multiple times. Will the page content stay the same or change?
- Open a new browser tab.
- Paste the ALB DNS name you copied earlier into the address bar and press Enter.
- Refresh the page several times.
The instance ID and Availability Zone change between refreshes. The ALB is distributing traffic across both instances in different Availability Zones.
Not seeing two different instance IDs?
- Check the Targets tab in ha-platform-tg. Both targets should show healthy. If one or both show unhealthy or initial, wait a few more minutes for the health check grace period to pass.
- Confirm the ASG is placed in the private subnets and the ALB is in the public subnets. Mixing them up prevents traffic from flowing.
- Verify app-sg allows inbound HTTP from alb-sg. If the security group rule references itself instead, the ALB cannot reach the instances.
Help me troubleshoot my ALB traffic distribution.
Traffic is flowing through the load balancer to instances in both Availability Zones. Next, you will prove you can administer these private instances securely through Session Manager, with no SSH ports open.
Verify Session Manager and Lock Down Access
Your Application Load Balancer is live and distributing traffic across two healthy instances in private subnets. That is a major upgrade from the single fragile instance you started with.
But the original instance from Step 1 is still running with SSH port 22 open to the entire internet. That is a security gap you need to close.
AWS Systems Manager Session Manager provides terminal access through the AWS Console without any open inbound ports. In this step, you will prove it works and then clean up the fragile setup for good.
In this step, get ready to:
- Connect to a private instance through Session Manager and confirm the web server responds.
- Verify that no SSH access exists anywhere in the architecture.
- Terminate the original fragile instance and delete its security group.
Connect to a private instance via Session Manager
Session Manager uses IAM for authentication instead of SSH keys. The SSM Agent, pre-installed on Amazon Linux 2023, connects outbound through the NAT gateway to reach the Systems Manager endpoint.
- Return to the EC2 console and click Instances in the left sidebar.
- Select one of the instances launched by your Auto Scaling Group.
- Click Connect at the top of the page.
- Select the Session Manager tab.
Before you click Connect, think about this: the instance has no public IP and no SSH port open. Do you think you will still get a terminal session?
- Click Connect.
A browser-based terminal opens with a shell prompt. You got in without SSH, without a public IP, and without a bastion host.
- Confirm the web server is running by entering this command in the Session Manager terminal:
curl localhost
You should see the HTML output of your web page, including the instance ID and Availability Zone.
Why does Session Manager work without SSH?
The SSM Agent on each instance initiates an outbound HTTPS connection to the Systems Manager endpoint through the NAT gateway. Because the connection is outbound, no inbound security group rule is needed.
Authentication is handled by the AmazonSSMManagedInstanceCore IAM policy you attached to the instance role in Step 3. Every session is logged and auditable, which is a significant security improvement over raw SSH key access.
Session Manager Connect button grayed out?
- Make sure you selected an Auto Scaling Group instance, not the original fragile instance from Step 1. Only the ASG instances have the ssm-instance-role IAM role attached.
- The SSM Agent may still be registering with Systems Manager. Wait about a minute and refresh the page.
- Verify the instance's private subnet routes 0.0.0.0/0 to a NAT gateway. Without outbound internet, the agent cannot reach the SSM endpoint.
Help me troubleshoot why Session Manager cannot connect to my private EC2 instance.
Verify there is no SSH access
Session Manager works, but you should also confirm that the security group has no SSH rule at all. No rule means no attack surface.
- Close the Session Manager browser tab to return to the EC2 console.
- Click Security Groups under Network & Security in the left sidebar.
- Select the app-sg security group.
- Click the Inbound rules tab.
You should see exactly one rule: HTTP on port 80, with the source set to alb-sg. There is no port 22 rule anywhere.
- Click Instances in the left sidebar to return to the instances list.
- Select one of the Auto Scaling Group instances.
- In the Details tab, find the Public IPv4 address field.
The field shows a dash, meaning no public IP is assigned. Your application instances are fully private: no SSH rule, no public IP, and Session Manager handles all admin access through IAM.
What is least-privilege admin access?
Least-privilege means granting only the minimum permissions needed for a task. With SSH, anyone who finds the key and the IP can try to connect.
Session Manager restricts access to IAM identities with the right permissions. No ports, no keys, no bastion hosts.
Terminate the original fragile instance and clean up
The original instance from Step 1 is still running in the default VPC with SSH wide open. Now that you have a production-grade setup, it is time to remove that fragile setup entirely.
- In the Instances list, find the original instance from Step 1. It is the one with a value in the Public IPv4 address column (your ASG instances have none).
- Select the fragile instance.
- Click Instance state, then Terminate (delete) instance.
- Click Terminate (delete) to confirm.
The instance state changes to Shutting down and then Terminated. Wait until it reaches Terminated before continuing.
- Click Security Groups under Network & Security in the left sidebar.
- Select the fragile-sg security group.
- Click Actions, then Delete security groups.
- Type Delete in the confirmation field and click Delete.
Can't delete the security group?
- The security group cannot be deleted while it is still associated with an instance. If the fragile instance has not fully terminated yet, wait another minute and try again.
- Make sure you selected fragile-sg and not app-sg or alb-sg. Deleting the wrong security group would break your production setup.
Help me troubleshoot why I cannot delete my security group in the EC2 console.
The fragile instance was never part of your ALB or Auto Scaling Group. What do you expect to see when you refresh the ALB DNS name in your browser?
- Switch to the browser tab with your ALB DNS name and refresh the page several times.
Traffic still alternates between the two healthy instances in different Availability Zones. Removing the fragile instance changed nothing for your production traffic because it was completely separate from the ALB architecture.
What changed from Step 1 to now?
- Step 1 had one instance in a public subnet with SSH open to the world. One failure meant total downtime.
- Now you have two instances in private subnets behind an ALB, with no SSH ports open and admin access handled entirely through Session Manager.
- The only thing publicly exposed is the ALB. Your application servers are invisible to the internet.
Your architecture is locked down and running cleanly. Next up, you will put it to the ultimate test by terminating a live instance and watching the system heal itself.
Break It and Watch It Heal
Your platform now has an ALB distributing traffic across two private instances, with Session Manager as the only admin access method. Everything looks healthy on paper. But does it actually survive a failure?
The whole point of this architecture is resilience. In this step, you will deliberately terminate one of your running instances and watch what happens. If the system works correctly, users hitting the load balancer should experience zero downtime while the Auto Scaling Group detects the failure and automatically launches a replacement.
In this step, get ready to:
- Terminate one of your running instances to simulate a real failure.
- Observe the ALB routing traffic only to the surviving instance with no downtime.
- Confirm the Auto Scaling Group launches a replacement that passes health checks automatically.
Note your instance IDs and terminate one instance
Before you break anything, you need a baseline. You will load the ALB page in your browser to see which two instances are currently serving traffic, then deliberately kill one.
- Open a new browser tab.
- Paste the ALB DNS name into the address bar and press Enter. You can find the DNS name in the EC2 console under Load Balancers in the left navigation pane.
- Refresh the page several times. You should see the page alternate between two different instance IDs in two different Availability Zones.
Make a mental note of both instance IDs. You are about to terminate one of them.
- Keep this ALB browser tab open. You will come back to it in a moment.
- Switch to the AWS Console tab and click Instances in the left navigation pane of the EC2 console.
- Select the checkbox next to one of the two running instances managed by your Auto Scaling Group.
- Click Instance state at the top of the page.
- Click Terminate (delete) instance.
- Click Terminate (delete) to confirm.
The instance state changes to shutting-down. This is your simulated failure. In production, this could be a hardware crash, a kernel panic, or a misconfigured deployment that kills the process.
Why terminate instead of stop?
Terminating fully destroys the instance, which is a more realistic failure scenario than stopping. A stopped instance can be restarted. A terminated instance is gone forever, forcing the Auto Scaling Group to launch a brand new replacement from the launch template.
Watch the ALB handle the failure
The instance is dying. What happens to the users who are visiting your website right now?
Before you check, take a guess: do you think refreshing the ALB page will show an error, or will it keep working?
- Switch back to the browser tab with the ALB DNS name.
- Refresh the page repeatedly.
You should see only one instance ID now. The terminated instance no longer appears. The ALB detected that the instance was unhealthy and stopped routing traffic to it. There is no downtime because the surviving instance kept serving every request.
How does the ALB know to stop sending traffic?
The ALB runs health checks against each registered target every 30 seconds. When a target fails 2 consecutive health checks (the default UnhealthyThresholdCount), the ALB marks it as unhealthy and removes it from the rotation.
A terminated instance fails health checks immediately because it no longer responds on port 80. The ALB simply routes all traffic to the remaining healthy instance.
- Return to the AWS Console and click Auto Scaling Groups in the left navigation pane (under Auto Scaling).
- Click on your Auto Scaling Group name to open its details.
- Click the Activity tab.
You should see a new activity entry showing that the ASG detected the terminated instance and is launching a replacement. The desired capacity is 2, but only 1 instance is running, so the ASG is working to close that gap.
Give it a minute or two
The replacement instance needs time to launch, boot, install the web server via the user data script, and pass its health checks. This process typically takes 2-3 minutes. Keep refreshing the Activity tab to watch the progress.
Confirm the system self-healed
The Auto Scaling Group has launched a replacement instance. Now you need to confirm it actually passed its health checks and started receiving traffic.
- In the left navigation pane of the EC2 console, click Target Groups (under Load Balancing).
- Click on your target group name to open its details.
- Click the Targets tab.
What do you expect to see? Before you scroll down, predict: will the new instance already be healthy, or will it still be in another state?
If a couple of minutes have passed since the termination, you should see two registered targets. The new instance will be in one of two states depending on timing:
- Status initial means the ALB is still running its first health checks against the new instance. Keep refreshing the page.
- Status healthy means the instance passed its health checks and is now receiving traffic from the ALB.
Why does the new instance start as initial?
The ALB requires 5 consecutive successful health checks (the default HealthyThresholdCount) before marking a new target as healthy. With health checks running every 30 seconds, this takes about 2.5 minutes.
- Wait until both targets show a Health status of healthy. Refresh the Targets tab if needed.
Once both targets are healthy, it is time for the final test.
- Switch back to the browser tab with the ALB DNS name.
- Refresh the page several times.
You should see the page alternating between two instance IDs across two Availability Zones again. One of the instance IDs is new. The Auto Scaling Group launched a replacement, the ALB verified it was healthy, and traffic is now being distributed across both AZs. The system self-healed without any manual intervention.
Still seeing only one instance ID?
If only one instance ID appears when refreshing the ALB page, the replacement instance might still be booting. Go back to the Targets tab in your target group and check whether the second target is still in initial status. Wait for it to reach healthy, then try refreshing the ALB page again.
If several minutes have passed and the target remains in initial or shows as unhealthy, check that the app-sg security group still allows inbound HTTP on port 80 from alb-sg. Also verify the launch template user data script is correct, because the new instance uses the same launch template to install and start the web server.
Need more help? Help me troubleshoot why my replacement instance is not passing health checks.
Secret mission
Add Auto Scaling Based on CPU
Your platform maintains a fixed number of instances right now. What happens when traffic spikes 10x? In this mission, you will add a target tracking scaling policy so the Auto Scaling Group automatically launches more instances when CPU utilization climbs and removes them when demand drops.
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 NAT gateways and an Application Load Balancer that cost money while running, even when no traffic is flowing.
Cost warning
The 2 NAT gateways cost $0.045/hour each ($0.09/hour total). The ALB costs $0.0225/hour. Together, that is roughly $0.11/hour or $2.70/day. The t2.micro EC2 instances are free tier eligible for the first 750 hours/month on new accounts.
If you are not actively working on this project, delete all resources to avoid unexpected charges.
Resources you used:
- Auto Scaling Group (with 2 EC2 instances and scaling policies).
- Application Load Balancer and target group.
- Launch template.
- VPC ha-platform (with 2 public subnets, 2 private subnets, 2 NAT gateways, internet gateway, route tables, and security groups).
- Elastic IPs associated with the NAT gateways.
- IAM role ssm-instance-role (with instance profile).
Keep everything running
No action needed. Choose this if you are still actively building or planning to extend this project.
- Your resources will continue running. Costs accumulate at roughly $0.11/hour ($2.70/day), mainly from the NAT gateways and ALB.
- The t2.micro instances are free tier eligible for new accounts (750 hours/month shared across all t2.micro instances in the account).
- The Elastic IPs are free while attached to running NAT gateways.
Pause - I'll come back to this later
Reduce costs by scaling down your instances while keeping the network infrastructure intact.
- In the EC2 console, select your Auto Scaling Group under Auto Scaling Groups.
- Choose the Details tab, then click Edit next to Group details.
- Set Desired capacity, Minimum capacity, and Maximum capacity all to 0, then click Update.
- This terminates all EC2 instances and stops instance-related costs.
NAT gateways and ALB still cost money
Even with zero instances, the NAT gateways ($0.09/hour) and ALB ($0.0225/hour) continue to incur charges. If you plan to be away for more than a few hours, the Delete option saves more money.
Delete - I don't want to use this again
Remove all project resources to stop all charges. Delete in this order to avoid dependency errors.
- Delete the Auto Scaling Group: In the EC2 console, choose Auto Scaling Groups from the navigation pane. Select your Auto Scaling Group, choose Actions, then Delete. Type delete to confirm. This terminates all EC2 instances and removes the scaling policies and CloudWatch alarms.
- Delete the Application Load Balancer: In the EC2 console, choose Load Balancers from the navigation pane. Select your ALB, choose Actions, then Delete load balancer. Type confirm and choose Delete.
- Delete the target group: In the EC2 console, choose Target Groups under Load Balancing. Select your target group, choose Actions, then Delete. Confirm the deletion.
- Delete the launch template: In the EC2 console, choose Launch Templates from the navigation pane. Select your launch template, choose Actions, then Delete template. Type Delete to confirm.
- Delete the NAT gateways: Open the VPC console. Choose NAT gateways from the navigation pane. Select each NAT gateway associated with ha-platform, choose Actions, then Delete NAT gateway. Type delete to confirm. Repeat for the second NAT gateway. Wait for both to show a Deleted status before proceeding.
- Delete the VPC: In the VPC console, choose Your VPCs. Select the ha-platform VPC, choose Actions, then Delete VPC. The console shows the resources it will delete (subnets, route tables, internet gateway, security groups). Type delete and choose Delete.
- Release the Elastic IPs: In the VPC console, choose Elastic IPs from the navigation pane. Select the Elastic IPs that were associated with your NAT gateways (they now show as disassociated). Choose Actions, then Release Elastic IP addresses. Confirm the release.
- Delete the IAM role: Open the IAM console at https://console.aws.amazon.com/iam/. Choose Roles from the navigation pane. Search for ssm-instance-role, select it, and choose Delete. Enter the role name to confirm and choose Delete. The console automatically detaches managed policies and removes the instance profile.
Verify everything is gone
After deletion, check the EC2 console for any remaining instances, load balancers, or Auto Scaling Groups. Check the VPC console to confirm ha-platform no longer appears. If any resource shows a dependency error, wait a minute and try again.
Nice Work!
Nice Work!
You just built a production-style highly available web application platform on AWS. You started with a single fragile EC2 instance, then systematically replaced every weakness with resilient, self-healing infrastructure.
You've learned how to:
- Build a VPC with public and private subnet isolation across two Availability Zones, with NAT gateways and route tables that separate public-facing components from private application servers.
- Deploy an Application Load Balancer that distributes traffic to healthy instances launched by an Auto Scaling Group, and prove the architecture self-heals by terminating an instance and watching automatic recovery.
- Configure Session Manager for secure admin access to private instances with no SSH ports open to the internet, using IAM for authentication instead of SSH keys.
- Completed an optional Secret Mission to push your cloud architecture skills further.
Ready to quiz yourself?