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
- Upstream and Load Balancing
- Node.js installed (for minimal test servers)
Step 1: Two Backend Processes
server-a.js:
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:
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:
127.0.0.1 lb.demo.localEnable:
sudo ln -sf /etc/nginx/sites-available/lb-demo /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxStep 3: Observe Round-Robin
for i in 1 2 3 4 5 6; do
curl -s http://lb.demo.local/
doneExpected alternating output:
backend-3001
backend-3002
backend-3001
...Step 4: Failover Test
Stop one server:
kill %1
for i in 1 2 3; do curl -s http://lb.demo.local/; doneAll 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)
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.logshows upstream errors when backend down -
nginx -tpasses
Real-World Notes
- Production uses systemd or PM2 for auto-restart of Node/Java instances
- Health checks beyond passive
max_failsneed modules or external load balancer - Sticky sessions:
ip_hashif 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.