Nginx Load Balancing Lab on AWS EC2

Nginx Load Balancing Lab on AWS EC2 — Romanch Jung Rayamajhi
Back to Portfolio
Nginx AWS EC2 DevOps

Nginx Load Balancing Lab on AWS EC2

A hands-on lab guide to setting up Nginx as a load balancer across multiple EC2 instances — covering round-robin, least connections, IP hash, and health checks.

RJ
Romanch Jung Rayamajhi
April 2026
10 min read
Architecture Overview
Clients users / traffic
Nginx LB :80 load balancer
Backend 1 :8080 app server
Backend 2 :8080 app server
Backend 3 :8080 app server

Nginx distributes traffic across 3 backend EC2 instances inside the default VPC

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)
ℹ️
We use 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

PortSourcePurpose
22Your IPSSH
800.0.0.0/0HTTP traffic

SG: backend-sg

PortSourcePurpose
22Your IPSSH
8080load-balancer-sgApp 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-sg
  • backend-1, backend-2, backend-3 — attach backend-sg
Note down the private IPs of all 3 backend instances — you'll need them for the Nginx upstream config.

STEP 03Set Up Backend Servers

SSH into each backend instance and run a simple Python HTTP server that identifies itself:

terminal — on each backend (1, 2, 3)
# 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 &
⚠️
Change the message to "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:

terminal — nginx-lb instance
# Update and install Nginx
sudo apt update && sudo apt install -y nginx

# Verify
nginx -v

4.1 Configure Upstream & Load Balancing

/etc/nginx/sites-available/loadbalancer
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;
    }
}
terminal — enable config
# 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.

round robin
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.

least connections
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.

ip hash
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.

weighted round robin
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;
}
💡
After changing the upstream block, always run 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:

health check config
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 down
  • fail_timeout=30s — wait 30 seconds before trying the failed server again
  • backup — 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:

terminal — from your local machine
# 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:

terminal — on backend-2
# 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
🚀
It works! Nginx detected the failed backend and automatically routed traffic to healthy servers — zero downtime for users.

STEP 08Monitoring & Logs

terminal — useful commands
# 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

⚠️
Don't forget! Terminate all 4 EC2 instances when you're done to avoid charges. Go to EC2 Dashboard → select all instances → Instance state → Terminate.

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

Comments

Popular posts from this blog

Deploy Quasar + Laravel + MySQL on AWS EC2 (Default VPC)

EC2 Fundamentals