Project: Simple Load Balancer

Introduction

This project runs two identical Node HTTP servers on ports 3001 and 3002, fronts them with an Nginx upstream group, and observes round-robin and failover when one instance stops.

Prerequisites

Step 1: Two Backend Processes

server-a.js:

javascript
const http = require("http");
 
const port = 3001;
const server = http.createServer((_req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("backend-3001\n");
});
 
server.listen(port, "127.0.0.1", () => {
  console.log(`Listening on 127.0.0.1:${port}`);
});

server-b.js — same with port 3002 and body backend-3002.

Run both:

bash
node server-a.js &
node server-b.js &

Or use PM2 for persistence—this exercise uses foreground/background for simplicity.

Step 2: upstream Config

/etc/nginx/sites-available/lb-demo:

Add to /etc/hosts:

text
127.0.0.1 lb.demo.local

Enable:

bash
sudo ln -sf /etc/nginx/sites-available/lb-demo /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Step 3: Observe Round-Robin

bash
for i in 1 2 3 4 5 6; do
  curl -s http://lb.demo.local/
done

Expected alternating output:

text
backend-3001
backend-3002
backend-3001
...

Step 4: Failover Test

Stop one server:

bash
kill %1
for i in 1 2 3; do curl -s http://lb.demo.local/; done

All responses should come from the surviving backend after Nginx marks the other down (may take a failed request or two).

Restart stopped server and verify both rejoin rotation.

Step 5: Weighted (Optional)

nginx
upstream node_pool {
    server 127.0.0.1:3001 weight=3;
    server 127.0.0.1:3002 weight=1;
}

Run curl loop again—roughly 3

ratio.

Acceptance Checklist

  • Round-robin visible with both servers up
  • Single server survives when other killed
  • error.log shows upstream errors when backend down
  • nginx -t passes

Real-World Notes

  • Production uses systemd or PM2 for auto-restart of Node/Java instances
  • Health checks beyond passive max_fails need modules or external load balancer
  • Sticky sessions: ip_hash if needed—see upstream chapter

FAQ

Both return same body?

Verify different ports and processes actually running (ss -tlnp).

No alternation?

Keep-alive reuse may hit same upstream—use curl without keepalive or multiple clients.

Apply to Spring Boot?

Run two JARs on 8081/8082 with same upstream block.

Docker replicas?

Publish two container ports or use compose scale + upstream to service IPs.

Clean up?

Kill node jobs; remove sites-enabled/lb-demo symlink; reload Nginx.