You should have a VPC and two EC2 instances already running (one in each AZ). The web servers should be listening on HTTP:80 (NGINX in this demo), and you should have a default route to an Internet Gateway for public subnets. If you plan to use HTTPS later, have a certificate ready in AWS Certificate Manager (ACM).
- Two EC2 instances:
web-server1andweb-server2 - Deployed in different AZs (us-east-1a and us-east-1b)
- Running NGINX with the default page modified to show which server handled the request (H1 shows “this is server one” or “this is server two”)
- Instances currently have public IPs (we’ll discuss hardening later)

http://54.210.5.199) to confirm the NGINX page displays the modified H1 indicating which server responded.

The VPC now contains four subnets: two web server subnets (one per AZ) and the two ALB subnets above. Because this ALB is internet-facing, the ALB subnets must be public (route to an Internet Gateway).
Creating the Application Load Balancer (ALB)
From the EC2 console → Load Balancers, choose Create load balancer → Application Load Balancer. AWS offers multiple load balancer types; choose the ALB for HTTP/HTTPS traffic:

- Name the load balancer (example:
web-lb) - Scheme: Internet-facing
- IP address type: IPv4 (or
dualstackfor IPv6 + IPv4)

lb-us-east-1a and lb-us-east-1b) — do not select the web server subnets here. ALB nodes will run in the lb-* subnets and forward traffic to targets in the web subnets.
Security groups
Assign a security group to the ALB that allows inbound HTTP (port 80) and/or HTTPS (port 443). In this demo a security group named launch-wizard-16 was pre-created to allow these ports.

- Listener: Defines how the ALB accepts incoming traffic (protocol + port). For this demo, use HTTP on port 80.
- Target group: Logical group of targets that receive traffic from the ALB (EC2 instances in this case). Configure protocol and port to match what the web servers are listening on (HTTP:80).
tg-web) with:
- Protocol: HTTP
- Port: 80
- VPC: same VPC as the instances
- Health check: HTTP against
/(or your app’s/healthendpoint)


web-server1 and web-server2 as targets on port 80. Initially they will appear as pending until health checks pass and the ALB marks them healthy.

- In the ALB creation wizard, set the listener’s default action to forward to
tg-web. - Confirm security groups and subnet selections.
- Create the load balancer.


http://web-lb-xxxx.elb.amazonaws.com) and use that for testing.

http://web-lb-xxxx.elb.amazonaws.com. Refresh repeatedly to observe the ALB distributing requests between the two instances — the H1 should alternate between “this is server one” and “this is server two”.
You can also test from the command line. Example (replace with your ALB DNS name):
-
Single request:
curl http://web-lb-xxxx.elb.amazonaws.com -
Repeated requests to observe distribution:
for i in {1..6}; do curl -s http://web-lb-xxxx.elb.amazonaws.com | sed -n 's/.*<h1>\(.*\)<\/h1>.*/\1/p'; sleep 1; done
- Move backend instances to private subnets (no public IPs). Keep ALB in public subnets to receive internet traffic and forward to private backends.
- Restrict backend security groups so they only accept inbound traffic from the ALB security group (allow traffic on port 80 only from the ALB SG).

Best practice: Put backend servers in private subnets and restrict their security groups so they only accept traffic from the load balancer. This ensures clients cannot bypass the ALB and improves the overall security posture.
- Application Load Balancer concepts: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html
- Target groups and health checks: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html
- Security groups for load balancers: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_SecurityGroups.html