Building an AWS Application Load Balancer with Two EC2 Servers

A hands-on lesson from my AWS journey on launching multiple EC2 instances from a template, grouping them inside a target group, and placing an Application Load Balancer in front so traffic can be routed through one public endpoint.

This was one of the most practical AWS labs I’ve done so far because it finally connected multiple cloud concepts into one working setup. Instead of thinking about one EC2 instance in isolation, this lesson focused on how real web traffic is usually handled in cloud environments: multiple backend servers, one frontend endpoint, health checks, and controlled routing.

The Problem with One Web Server

A single web server may be enough for a basic test, but it is not a strong production pattern. If traffic increases, performance can drop. If maintenance is needed, users may be affected. If the instance goes down, the whole site disappears.

The better pattern is to run multiple web servers and place a load balancer in front of them so users still see one entry point.

That is the core idea behind this lab. Two EC2 instances run the same web application, and the Application Load Balancer distributes traffic between them.

The Final Architecture

User
  ↓
Application Load Balancer
  ↓
Target Group
  ↓
web-01      web-02

The user never needs to know which backend server handled the request. They just access the load balancer DNS name, and the load balancer handles the rest.

Step 1: Launch Two EC2 Instances from a Launch Template

The lab starts from the launch template created earlier. That template already contains the AMI and the rest of the important configuration needed to create identical servers quickly.

From the launch template page, the next step is:

Actions → Launch instance from template

Two instances are launched at the same time. Initially they share the same name tag pattern, and then they are renamed clearly as:

This is important because both servers should behave the same way, but it is still useful to identify them separately during testing and troubleshooting.

Why the Launch Template Matters Here

If I had to configure everything manually each time, creating multiple backend servers would become repetitive and error-prone. Using the launch template means both instances are consistent. That consistency matters a lot when placing them behind a load balancer.

Step 2: Create the Target Group

After the two instances are running, the next step is to create a Target Group. A target group is basically a group of backend resources that the load balancer will send traffic to. In this case, those resources are EC2 instances.

The target group is created with:

Since Apache is serving the website on port 80, the target group must match that.

Health Checks: The Most Important Part to Understand

One of the most useful parts of this lab was understanding health checks properly. The target group does not just blindly send traffic to any instance. It checks whether the instance is actually healthy enough to serve traffic.

In this lab, the health check configuration was based on:

Since the site is being served from the default web root, using / is correct. If the application lived under a different path, for example /app or /inner-peace, then the health check path would also need to match that.

A lot of load balancer problems are not application problems at all. They are often health check configuration problems.

That was a very useful takeaway. If the service is actually running, but the health check is pointing to the wrong protocol, wrong port, or wrong path, the instance will still be marked unhealthy.

Health Threshold Changes

The instructor reduced the healthy threshold from the default value to 2. That means the instance only needed two successful health checks before being marked healthy. This makes the lab faster, while still keeping the logic realistic enough for learning.

Registering Targets

After the target group is created, both instances are selected and added into it. This step matters because simply having running instances is not enough. The load balancer can only route traffic to instances that are actually registered inside the target group.

Step 3: Create the Application Load Balancer

The next step is creating the actual Application Load Balancer (ALB). This is the public entry point users will access from the browser.

The important settings in this lab were:

Internet-Facing vs Internal ALB

This part is important because AWS gives two choices: Internet-facing and Internal.

In this lab, the correct choice is Internet-facing because the site needs to be accessible from the browser over the internet.

Internet-facing ALB is public and meant for user traffic. Internal ALB is private and meant for service-to-service communication inside AWS.

An internet-facing ALB gets a public DNS name and is reachable from the internet. An internal ALB only uses private networking inside the VPC and is commonly used for backend services.

Internet-facing ALB → public websites and apps
Internal ALB       → backend APIs and internal services

This distinction is very useful in real architectures. For example, a frontend app might sit behind an internet-facing ALB, while the backend API might sit behind an internal ALB.

Subnets and Availability

Multiple subnets were selected during the ALB setup. This is part of making the load balancer available across the region. Even if the backend instances are currently in one specific zone, the ALB itself can be prepared to span across multiple subnets and availability zones.

Step 4: Create a Separate Security Group for the Load Balancer

One subtle but very important detail in the lab is that the load balancer gets its own security group. This is not the same security group as the EC2 instances.

For the ALB security group, inbound rules allow:

That makes sense because the ALB is the public entry point.

Step 5: Connect Listener to the Target Group

By default, the ALB listens on port 80. When a request arrives, the ALB needs to know where to send it. That routing is configured by pointing the listener to the target group created earlier.

HTTP : 80 → Forward to target group

At this point, the main setup is complete:

It feels like everything should work immediately. But this is exactly where one of the most important debugging lessons happens.

The Real Problem: Instances Were Unhealthy

After the ALB became active, opening the ALB DNS name in the browser still did not work. The load balancer was up, but the target group showed both instances as unhealthy. That is a very common real-world situation.

The first instinct might be to blame Apache or the application. But in this lab, the web servers were actually working. They were reachable by public IP directly. So the real issue had to be somewhere between the load balancer and the instances.

The Security Group Mistake

The EC2 instance security group originally allowed HTTP only from my own IP. That was fine for direct browser testing, but it did not allow HTTP from the load balancer.

So from AWS’s perspective:

This was the key lesson: the load balancer must be explicitly allowed in the instance security group.

The Fix

The solution was to update the EC2 security group:

In simple words, the EC2 instances should trust the ALB, not the whole internet.

Allow HTTP : 80
Source = ALB Security Group

After saving this rule, the health checks started passing. Within a short time, the target group marked both instances as healthy.

Step 6: Final Test

Once the instances became healthy, the ALB DNS endpoint started working correctly. At that point, the browser could access the website through the load balancer instead of directly through the instance IP.

That was the real success of the lab. The important thing was not just “the site opens.” The important thing was that it opens through the proper architecture:

User → ALB DNS → Target Group → Healthy EC2 instances

Why This Lab Matters So Much

This lab felt important because it brought together several AWS ideas into one real flow:

That is much closer to a real cloud deployment than simply launching one EC2 instance and opening it directly to the world.

What I Learned from the Security Side

One of the strongest lessons here was security group design. The backend instances should not simply accept public traffic if a load balancer is meant to sit in front of them. A better pattern is:

Internet → ALB
ALB → EC2 instances

Not:

Internet → EC2 directly

That small difference changes the architecture from a basic demo into a more production-like setup.

Cleanup and Resource Awareness

The lab also ends with an important cleanup reminder. After testing, the instances can be terminated, the load balancer deleted, and the target group cleaned up. AWS resources should not be left running without purpose.

One detail that remains is the AMI created earlier. That AMI still has a snapshot behind it, which is why the snapshot is not deleted immediately. It is tied to the image and can be used in later exercises.

Final Thoughts

This was one of the clearest examples of how AWS services work together. The Application Load Balancer was not difficult by itself, but the real learning came from understanding the full chain: instances, health checks, security groups, listeners, target groups, and DNS endpoint behavior.

The biggest takeaway for me was that real cloud architecture is not just about “making it work.” It is about making it work in the right flow, with clean routing and proper security boundaries.

After this lab, load balancers stopped feeling abstract. They started feeling like a practical and necessary part of building reliable web systems in AWS.

← Back to All Blogs