AWS VPC to Auto Scaling: Build a Self-Healing, Elastic Web Tier

Diagram of an AWS web tier: users, internet gateway, load balancer in public subnets, Auto Scaling servers in two zones

An AWS VPC, Auto Scaling and a load balancer together give you a web tier that keeps serving traffic when a server fails or demand rises. This guide walks through each layer in order. You start with an empty network and finish with private servers that replace themselves and scale with load, all reachable through one DNS name.

Each step builds on the previous one. The goal is for you to understand why each piece exists, not only which buttons to click. That understanding makes problems much easier to troubleshoot.

In short
  • Spread one VPC across two Availability Zones, with a public and a private subnet in each.
  • Route tables decide whether a subnet is public (internet gateway) or private (NAT gateway).
  • Chain security groups so only the load balancer can reach your app servers.
  • A launch template defines the servers, the ALB routes traffic to them, and Auto Scaling keeps them healthy and sized.
  • Verify healthy targets and an HTTP 200, then terminate an instance to watch the tier heal.
  1. 0:00Intro
  2. 0:29What you'll build and need
  3. 1:19How the pieces fit together
  4. 2:02Create the VPC and subnets
  5. 2:58Attach gateways and set routes
  6. 3:58Define security groups in layers
  7. 4:56Create a launch template and ALB
  8. 5:56Create the Auto Scaling group
  9. 6:50Verify healthy targets and traffic
  10. 7:38Fix the three common failures
  11. 8:24Everything we built, in order
  12. 8:51Resilient by design, elastic by default

Plan the web tier and gather what you need

Start by defining the finished result. You are building a web tier that spreads across two Availability Zones. A load balancer sits in front of it, and Auto Scaling decides how many servers run. If one data center goes dark, the other zone keeps serving. When traffic grows or shrinks, the number of servers follows.

A common question is how users reach many servers through a single address. The load balancer has one DNS name that users connect to. It sends requests only to servers that pass their health checks. A request enters the VPC through the internet gateway and reaches the load balancer in the public subnets. From there it goes to an app server in a private subnet. When those servers need updates, they reach the internet through a NAT gateway.

The core design rule is two of everything. Every tier spans two zones, so a problem in one zone does not take down your app. In this setup, that is what highly available means.

  • An AWS account
  • IAM permissions to create VPC and EC2 resources, including load balancers and instances
  • Basic networking knowledge: IP ranges and ports
  • A sample app AMI with your application already installed

Create the VPC and subnets across two zones

The network is the foundation, so it comes first. Open the VPC console and choose Create VPC. Then select "VPC and more" so the wizard builds the subnets for you. Set the IPv4 CIDR block to a range such as 10.0.0.0/16, which is a common choice. Set the number of Availability Zones to two, public subnets to two and private subnets to two. Then click Create VPC.

The result is one VPC with a public and a private subnet in each of two zones. Public subnets hold the load balancer and can be reached from the internet. Private subnets hold your app servers, and nothing on the internet can reach them directly. A subnet is not public because of a special setting. It is public because of its routes, which you check in the next step.

With this layout, the network is designed for failure and for safety from day one. Everything you add later sits inside this structure.

  • CIDR block set (for example 10.0.0.0/16)
  • Two Availability Zones selected
  • Two public and two private subnets created
  • Load balancer planned for the public subnets, app servers for the private ones

Attach gateways and set the route tables

Your new subnets cannot send or receive any traffic yet. In this step you attach an internet gateway to the VPC and add a NAT gateway in a public subnet. Then you point each route table at the right gateway. If the wizard already created these for you, use this step to check its work.

The key idea is that a route table alone makes a subnet public or private. The public route table has a local route for 10.0.0.0/16, which keeps traffic inside the VPC. It also has a 0.0.0.0/0 route that sends everything else to the internet gateway. The private route table looks the same, except its default route points to the NAT gateway.

The internet gateway works like a two-way door. Any subnet routed to it can both send and receive internet traffic. The NAT gateway only lets traffic start from the inside. Private servers can open connections outward, for example to download patches, while connections that start outside are blocked.

  • Internet gateway attached to the VPC
  • NAT gateway placed in a public subnet
  • Public route table: local route plus 0.0.0.0/0 to the internet gateway
  • Private route table: local route plus 0.0.0.0/0 to the NAT gateway
# public-rt (both public subnets)
10.0.0.0/16   local
0.0.0.0/0     igw-0f1e2d3c
# private-rt (both private subnets)
10.0.0.0/16   local
0.0.0.0/0     nat-0b9c8d7e

Chain security groups in layers

Routes decide where traffic can go. Security groups decide what traffic is allowed in. You will build two groups that work as a chain: the internet may reach the load balancer, and only the load balancer may reach your instances. Picture two locked doors, one behind the other.

First, add an inbound rule to the load balancer's group, sg-0a1b2c3d, allowing TCP port 443 from anywhere. Next, on the app group, sg-0e5f6a7b, allow port 80, but set the source to the load balancer's security group. AWS confirms each rule by returning true.

Pointing at the load balancer's security group instead of an IP range matters. The rule keeps working as Auto Scaling adds and removes instances, so you never need to update addresses. Security groups are also stateful, which means you only write inbound rules here. Responses to allowed traffic go back out automatically.

  • ALB group allows 443 from 0.0.0.0/0
  • App group allows the app port only from the ALB's security group
  • No IP ranges hard-coded for app servers
  • Only inbound rules needed, because security groups are stateful
aws ec2 authorize-security-group-ingress \
aws ec2 authorize-security-group-ingress \

Build a launch template and Application Load Balancer

With the network and rules in place, you can create the parts that run your app. In the EC2 console, open Create launch template and name it web-lt. Choose your sample app AMI. Pick an instance type, such as t3.micro, which is fine for testing. Select app-sg as the security group, then create the template.

Think of the launch template as a recipe. Every instance that Auto Scaling launches later is built from it. As a result, all your servers are identical, and none of them needs setup by hand.

Next, create an Application Load Balancer. Make it internet-facing, place it in both public subnets and attach alb-sg. Add a listener that forwards to a new target group called web-tg, and leave that target group empty for now. Auto Scaling will fill it in the next step. The template defines what to launch, and the load balancer decides where traffic goes.

  • Launch template web-lt uses the sample app AMI and app-sg
  • ALB is internet-facing and spans both public subnets
  • ALB uses alb-sg
  • Listener forwards to target group web-tg

Create the Auto Scaling group and scaling policy

This step makes the tier elastic. The Auto Scaling group's only job is to keep the right number of healthy servers running at all times. Create a group named web-asg that uses the launch template web-lt. Set a minimum size of 2 and a maximum of 6. These are example values, so adjust them to your workload. List both private subnets, attach the web-tg target group and set the health check type to ELB.

Then add a target tracking policy. You choose a metric, such as average CPU, and a target value. When load pushes CPU above the target, the group launches instances. When load drops, it removes them.

Setting the health check type to ELB lets the group heal itself. If the load balancer marks a server unhealthy, Auto Scaling terminates it and launches a fresh one. The system recovers on its own, without anyone stepping in.

  • Group uses launch template web-lt
  • Min and max sizes set (example: 2 and 6)
  • Both private subnets listed
  • Target group attached and health check type set to ELB
  • Target tracking policy on a metric such as average CPU
aws autoscaling create-auto-scaling-group \

Verify healthy targets and test a failure

Test the setup to confirm it works. Run describe-target-health on the target group. You should see two targets, one in each zone, both reporting healthy. Then send a request with curl to the load balancer's DNS name. A response of HTTP/1.1 200 OK means the request travelled all the way through and back.

Next, cause a failure on purpose. Terminate one instance from the console and keep refreshing the load balancer's address. The site should keep answering while the group launches a replacement. After a short wait, the target group should show two healthy targets again. That brings the count back to your minimum.

This test is the best evidence that the design works. You can watch the tier keep serving and recover by itself.

  • Two targets report healthy, one per zone
  • curl to the ALB DNS name returns HTTP 200
  • Site keeps responding after you terminate an instance
  • Healthy target count returns to the minimum
aws elbv2 describe-target-health \
curl -I http://$ALB_DNS

Fix the three most common failures

If a check fails, the cause is almost always one of three things: health checks, ports or routes. If targets stay unhealthy or keep draining, the health check path and port probably don't match what your app serves. Update them so they match. If the ALB returns 504 timeouts, a port is likely blocked. Allow the app port from the ALB's security group. If instances can't reach the internet, a route is missing. Add a 0.0.0.0/0 route to the internet gateway or the NAT gateway.

Work through the checks in a fixed order: routes first, then security groups, then health check settings. Moving outward from the network means you rule out the basics before looking at app details, so you avoid guessing.

  • Unhealthy or draining targets: match the health check path and port
  • 504 timeouts: allow the app port from the ALB security group
  • No internet from instances: add a 0.0.0.0/0 route to the IGW or NAT
  • Check in order: routes, security groups, health checks

Key takeaways

  • Two Availability Zones in one VPC give the app room to survive a zone failure.
  • Route tables, not subnet settings, make a subnet public or private.
  • Referencing the ALB's security group keeps rules working as instances change.
  • A launch template makes every new server identical and ready to use.
  • ELB health checks plus target tracking let the tier heal and scale on its own.
  • Always prove resilience by terminating an instance and watching recovery.

Frequently asked questions

Why put app servers in private subnets?

Private subnets have no route from the internet, so nothing outside can reach your servers directly. Users reach them only through the load balancer. The servers can still download updates through the NAT gateway.

What is the difference between an internet gateway and a NAT gateway?

An internet gateway is a two-way door: subnets routed to it can send and receive internet traffic. A NAT gateway lets private servers start outbound connections but blocks connections that start from outside.

Why reference a security group instead of IP addresses?

Auto Scaling adds and removes instances, so their addresses keep changing. A rule that allows traffic from the load balancer's security group keeps working no matter which instances exist.

How does Auto Scaling know when to add servers?

A target tracking policy watches a metric you choose, such as average CPU, and compares it with a target value. When the metric goes above the target, the group launches instances, and when it falls, the group removes them.

Why does my load balancer return 504 errors?

A 504 timeout usually means the load balancer can't reach your instances on the app port. Check that the app security group allows that port from the ALB's security group.

How can I test that the setup is highly available?

Terminate one instance while you keep refreshing the load balancer's address. The site should keep responding, and the target group should return to its minimum number of healthy targets after a short wait.

Watch the full video on YouTube →

Souy Soeng

Souy Soeng

Hi there 👋, I’m Soeng Souy (StarCode Kh)
-------------------------------------------
🌱 I’m currently creating a sample Laravel and React Vue Livewire
👯 I’m looking to collaborate on open-source PHP & JavaScript projects
💬 Ask me about Laravel, MySQL, or Flutter
⚡ Fun fact: I love turning ☕️ into code!

Post a Comment

CAN FEEDBACK
Ad