STEP 01Prerequisites & Lab Setup
For this lab, we'll spin up 4 EC2 instances total:
- 1 Nginx Load Balancer — receives all incoming traffic
- 3 Backend Servers — simple web servers that respond with their identity
You'll need:
- An AWS account with EC2 access
- A key pair for SSH
- All instances in the same VPC and subnet (default VPC works)
t2.micro (free tier) for all instances. This lab costs almost nothing if you clean up after.STEP 02Launch EC2 Instances
2.1 Security Groups
Create two security groups:
SG: load-balancer-sg
| Port | Source | Purpose |
|---|---|---|
| 22 | Your IP | SSH |
| 80 | 0.0.0.0/0 | HTTP traffic |
SG: backend-sg
| Port | Source | Purpose |
|---|---|---|
| 22 | Your IP | SSH |
| 8080 | load-balancer-sg | App traffic from LB only |
2.2 Launch Instances
Launch 4 instances — Ubuntu 22.04, t2.micro, all in the default VPC. Tag them:
nginx-lb— attach load-balancer-sgbackend-1,backend-2,backend-3— attach backend-sg
STEP 03Set Up Backend Servers
SSH into each backend instance and run a simple Python HTTP server that identifies itself:
# SSH into backend ssh -i "your-key.pem" ubuntu@<backend-private-ip> # Create a simple identifier page echo "Hello from Backend 1" > index.html # Start a simple HTTP server on port 8080 python3 -m http.server 8080 & # Or use a persistent approach with nohup nohup python3 -m http.server 8080 &
"Hello from Backend 2" and "Hello from Backend 3" on the other two servers. This is how you verify load balancing is working.STEP 04Install & Configure Nginx Load Balancer
SSH into the nginx-lb instance:
# Update and install Nginx sudo apt update && sudo apt install -y nginx # Verify nginx -v
4.1 Configure Upstream & Load Balancing
upstream backend_pool { # Default: Round Robin server 10.0.1.101:8080; # backend-1 private IP server 10.0.1.102:8080; # backend-2 private IP server 10.0.1.103:8080; # backend-3 private IP } server { listen 80; server_name _; location / { proxy_pass http://backend_pool; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Timeouts proxy_connect_timeout 5s; proxy_read_timeout 10s; } }
# Enable the site sudo ln -s /etc/nginx/sites-available/loadbalancer /etc/nginx/sites-enabled/ # Remove default sudo rm /etc/nginx/sites-enabled/default # Test & reload sudo nginx -t sudo systemctl reload nginx
STEP 05Load Balancing Algorithms
Nginx supports multiple algorithms. Change the upstream block to experiment:
Round Robin (default)
Distributes requests evenly across all servers, one after another.
upstream backend_pool { server 10.0.1.101:8080; server 10.0.1.102:8080; server 10.0.1.103:8080; }
Least Connections
Sends traffic to the server with the fewest active connections. Great for uneven workloads.
upstream backend_pool { least_conn; server 10.0.1.101:8080; server 10.0.1.102:8080; server 10.0.1.103:8080; }
IP Hash (Sticky Sessions)
Same client always goes to the same backend. Useful when you need session persistence.
upstream backend_pool { ip_hash; server 10.0.1.101:8080; server 10.0.1.102:8080; server 10.0.1.103:8080; }
Weighted
Send more traffic to stronger servers.
upstream backend_pool { server 10.0.1.101:8080 weight=5; # gets 5x traffic server 10.0.1.102:8080 weight=3; server 10.0.1.103:8080 weight=1; }
sudo nginx -t then sudo systemctl reload nginx.STEP 06Health Checks & Failover
Nginx automatically performs passive health checks. If a backend fails, Nginx stops sending traffic to it:
upstream backend_pool { server 10.0.1.101:8080 max_fails=3 fail_timeout=30s; server 10.0.1.102:8080 max_fails=3 fail_timeout=30s; server 10.0.1.103:8080 max_fails=3 fail_timeout=30s; # Optional: backup server (only used when all others are down) # server 10.0.1.104:8080 backup; }
What this does:
max_fails=3— after 3 failed requests, mark the server as downfail_timeout=30s— wait 30 seconds before trying the failed server againbackup— only receives traffic when all primary servers are down
STEP 07Testing the Load Balancer
Hit the load balancer's public IP repeatedly and watch the responses rotate:
# Hit the LB 9 times and see round-robin in action for i in {1..9}; do curl http://<nginx-lb-public-ip> done # Expected output: Hello from Backend 1 Hello from Backend 2 Hello from Backend 3 Hello from Backend 1 Hello from Backend 2 Hello from Backend 3 Hello from Backend 1 Hello from Backend 2 Hello from Backend 3
Test Failover
Stop one backend and see Nginx automatically route around it:
# Kill the web server on backend-2 pkill -f "http.server" # Now curl the LB again — backend-2 is skipped Hello from Backend 1 Hello from Backend 3 Hello from Backend 1 Hello from Backend 3
STEP 08Monitoring & Logs
# Watch access logs in real-time sudo tail -f /var/log/nginx/access.log # Watch error logs (see failed backends) sudo tail -f /var/log/nginx/error.log # Check Nginx status sudo systemctl status nginx # Check active connections # Add this location block to your config: location /nginx_status { stub_status; allow 127.0.0.1; deny all; } # Then: curl http://localhost/nginx_status
STEP 09Cleanup
Key Takeaways
- Round Robin — simple, even distribution (default)
- Least Connections — best for uneven workloads
- IP Hash — session persistence (sticky sessions)
- Weighted — route more traffic to stronger servers
- Passive health checks — Nginx auto-detects failures
- Backup servers — last resort when all primaries fail