Nginx Load Balancing: Cân bằng tải cho hệ thống Node.js trong 10 phút
Nếu ứng dụng Node.js của bạn bắt đầu quá tải, đã đến lúc nghĩ đến việc scale ngang (thêm nhiều instance thay vì nâng cấp server). Và để phân phối đều request đến các instance đó, bạn cần một Load Balancer. Nginx là lựa chọn hàng đầu nhờ hiệu năng cao, cấu hình đơn giản và hoàn toàn miễn phí (bản Open Source).
Bài viết này hướng dẫn bạn cấu hình Nginx Load Balancing cho hệ thống Node.js chỉ trong 10 phút, giải quyết triệt để bài toán giữ session đăng nhập khi user nhảy server.
Lưu ý: Nếu bạn chưa biết cách cài đặt Nginx làm reverse proxy cho một Node.js đơn lẻ, hãy xem lại bài “Cấu hình Nginx Reverse Proxy” trước khi bắt đầu.
Khi nào bạn cần Load Balancing?

Load Balancing không phải thứ bạn cài đặt ngay từ ngày đầu. Bạn cần nó khi:
- CPU hoặc RAM của server liên tục > 80% trong giờ cao điểm.
- Số lượng kết nối đồng thời (concurrent connections) vượt ngưỡng mà Node.js (single-thread) có thể xử lý mượt.
- Bạn muốn hệ thống High Availability – nếu một instance Node.js chết, traffic vẫn được chuyển sang instance khác.
- Bạn cần bảo trì (update code) mà không làm gián đoạn người dùng – dùng kỹ thuật “rolling update” qua load balancer.
Tóm lại: Load Balancer đứng trước nhiều instance Node.js, giống như một “lễ tân” thông minh, điều phối khách (request) đến các phòng (instance) rảnh nhất.
Cấu trúc Upstream trong Nginx
Trong Nginx, khối upstream dùng để định nghĩa một nhóm các server backend (Node.js instance). Đây là trái tim của Load Balancing.
Cú pháp cơ bản
Mở file cấu hình Nginx (thường là /etc/nginx/nginx.conf hoặc /etc/nginx/sites-available/example.com), thêm khối này bên ngoài khối server:
upstream nodejs_cluster {
# Các Node.js instance chạy trên cùng máy hoặc khác máy
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://nodejs_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Giải thích:
upstream nodejs_cluster– tên cluster bạn tự đặt.server 127.0.0.1:3001– mỗi dòng là một backend. Có thể là IP/Domain + port. Ở đây giả sử 3 instance Node.js chạy local ở cổng 3001, 3002, 3003.proxy_pass http://nodejs_cluster– thay vì trỏ proxy_pass đến một URL cụ thể, bạn trỏ đến tên upstream vừa tạo.
Sau khi lưu file, kiểm tra và reload Nginx:
nginx -t
systemctl reload nginx
Lúc này Nginx đã phân phối request theo thuật toán mặc định Round Robin.
Các thuật toán cân bằng tải phổ biến

Dựa theo tài liệu chính thức của Nginx, dưới đây là 3 thuật toán thường dùng nhất với bản Open Source.
1. Round Robin (mặc định)
Mỗi request luân phiên gửi đến từng server theo vòng tròn. Ví dụ: request 1 → server1, request 2 → server2, request 3 → server3, request 4 → server1…
Cấu hình: (không cần thêm directive nào)
upstream nodejs_cluster {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
2. Least Connections (ít kết nối nhất)
Request được gửi đến server đang có ít kết nối đang xử lý nhất. Phù hợp khi mỗi request có thời gian xử lý khác nhau.
upstream nodejs_cluster {
least_conn;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
3. IP Hash – Giải pháp cho Sticky Sessions
Đây là phần quan trọng nhất nếu ứng dụng Node.js của bạn lưu session ở memory (như express-session mặc định dùng MemoryStore) hoặc login state dựa trên session cookie.
Với Round Robin, user đăng nhập ở instance 3001, request tiếp theo lại rơi vào 3002 → bị mất login. IP Hash giải quyết bằng cách: tính hash từ địa chỉ IP của client, đảm bảo client luôn được gửi đến cùng một backend (trừ khi backend đó chết).
upstream nodejs_cluster {
ip_hash;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
Lưu ý: Nếu thêm hoặc bớt server, IP Hash có thể thay đổi ánh xạ, khiến user bị redirect đến server khác. Giải pháp tốt hơn cho production là dùng Redis hoặc JWT stateless thay vì session memory. Nhưng nếu chưa thể thay đổi kiến trúc, ip_hash là cứu cánh.
⚠️ Cảnh báo: Nginx Plus (bản trả phí) hỗ trợ sticky cookie chính xác hơn IP Hash. Bản Open Source không có sticky session dựa trên cookie. Hãy cân nhắc.
Thực hành: Cấu hình Nginx Load Balancer cho 3 App Node.js
Bây giờ, chúng ta sẽ đi từ A đến Z: chạy 3 instance Node.js đơn giản và cấu hình Nginx load balancing.
Bước 1: Tạo 3 file Node.js app đơn giản
Tạo thư mục load-balance-demo, bên trong tạo 3 file:
app1.js
const express = require('express');
const app = express();
const port = 3001;
app.get('/', (req, res) => {
res.send(`Hello from Node.js instance 1 (PID: ${process.pid})`);
});
app.listen(port, () => {
console.log(`App1 running on http://localhost:${port}`);
});
app2.js – tương tự, đổi port 3002 và text “instance 2”.
app3.js – tương tự, đổi port 3003 và text “instance 3”.
Chạy 3 instance trong 3 terminal riêng:
node app1.js
node app2.js
node app3.js
Bước 2: Cấu hình Nginx upstream (có ip_hash)
Sửa file cấu hình Nginx:
upstream nodejs_cluster {
ip_hash; # giữ session theo IP
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
server_name lab.example.com; # dùng tên miền hoặc localhost để test
location / {
proxy_pass http://nodejs_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
}
Proxy WebSocket? Các dòng Upgrade và Connection giúp hỗ trợ WebSocket nếu app Node.js của bạn dùng socket.io.
Bước 3: Reload Nginx và test
nginx -t && systemctl reload nginx
Mở trình duyệt gõ http://lab.example.com (hoặc http://localhost nếu bạn dùng server_name localhost). Refresh nhiều lần, bạn sẽ luôn thấy cùng một instance (nhờ ip_hash). Nếu bạn comment ip_hash đi, mỗi lần refresh sẽ luân phiên 3 instance.
Bước 4: Mô phỏng một instance bị chết
Dừng app2 (Ctrl+C). Gửi request liên tục, bạn sẽ thấy Nginx vẫn gửi request đến instance đã chết (vì Nginx Open Source mặc định không có active health check). Sau khoảng 2-3 lần thất bại, Nginx sẽ tự động loại instance đó khỏi vòng quay nếu bạn cấu hình max_fails và fail_timeout.
Thêm vào block server trong upstream:
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3003 max_fails=3 fail_timeout=30s;
max_fails=3– sau 3 lần thất bại (kết nối timeout hoặc lỗi HTTP 5xx), Nginx cho rằng server chết.fail_timeout=30s– trong 30 giây tiếp theo, Nginx không gửi request đến server đó. Sau 30s, thử lại.
Đây là cơ chế passive health check – chỉ phát hiện lỗi khi có request thực tế. Nginx Plus hỗ trợ active health check (gửi request kiểm tra định kỳ).
Lỗi thường gặp và cách khắc phục

Lỗi 1: Server die nhưng Nginx vẫn gửi traffic tới
Nguyên nhân: Không cấu hình max_fails và fail_timeout.
Cách sửa: Như đã hướng dẫn ở trên. Ngoài ra, bạn có thể dùng proxy_next_upstream để chỉ định trường hợp nào Nginx thử server tiếp theo.
location / {
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_pass http://nodejs_cluster;
}
Lỗi 2: User bị đăng xuất liên tục
Nguyên nhân: Không dùng ip_hash hoặc sticky sessions, session lưu trong RAM.
Cách sửa: Chuyển session sang Redis hoặc dùng ip_hash. Nếu dùng ip_hash mà vẫn bị logout, kiểm tra xem header X-Real-IP có gửi đúng IP client không.
Lỗi 3: WebSocket không hoạt động qua load balancer
Nguyên nhân: Thiếu các header Upgrade và Connection.
Cách sửa: Thêm các dòng proxy_set_header như trong ví dụ thực hành.
Best Practices khi dùng Nginx Load Balancer cho Node.js
-
Luôn dùng HTTPS ở tầng Load Balancer
Cấu hình SSL trên Nginx, để Nginx xử lý mã hóa/giải mã, Node.js chỉ nhận HTTP plaintext → giảm tải CPU cho app. -
Sử dụng Docker để quản lý các instance app dễ dàng hơn
Docker Compose giúp bạn scale nhanh:docker-compose up --scale app=3. Kết hợp với Nginx sử dụng DNS resolver động (cần cấu hìnhresolvervà biến trongproxy_pass). -
Đặt
keepaliveđể tái sử dụng kết nối đến backendupstream nodejs_cluster { keepalive 32; server 127.0.0.1:3001; }Kết hợp với
proxy_http_version 1.1vàproxy_set_header Connection ""để giảm overhead kết nối TCP. -
Giám sát upstream với stub_status module
location /nginx_status { stub_status; allow 127.0.0.1; deny all; } -
Dùng log để theo dõi upstream nào xử lý request
Thêm$upstream_addrvào log_format:log_format main '$remote_addr - $upstream_addr - $request';
FAQ
Load Balancer có làm app chậm đi không?
Có một chút (thường < 5ms latency) vì Nginx phải xử lý và chuyển tiếp request. Tuy nhiên lợi ích từ việc phân tán tải lớn hơn nhiều so với độ trễ nhỏ này. Nginx được viết bằng C, rất nhẹ.
Cần ít nhất bao nhiêu server để chạy load balancing?
Bạn có thể chạy Nginx và 2+ Node.js instance trên cùng một máy chủ (dùng các port khác nhau). Về mặt kỹ thuật, ít nhất 2 backend để thấy hiệu quả phân tải. Tuy nhiên nếu muốn High Availability cho chính Nginx, bạn cần 2 máy chủ riêng cho Nginx kết hợp với Keepalived hoặc cân bằng tải DNS.
Nginx Open Source có health check không?
Có nhưng chỉ là passive (phát hiện qua lỗi request thực). Nginx Plus có active health check (chủ động ping). Với bản Open Source, bạn có thể dùng thêm công cụ như consul hoặc aws alb để thay thế.
Kết luận
Chỉ với vài dòng cấu hình upstream và proxy_pass, bạn đã biến Nginx thành một Load Balancer mạnh mẽ cho hệ thống Node.js. Từ Round Robin đơn giản đến ip_hash xử lý session, từ passive health check đến các best practices, bài viết đã trang bị cho bạn đủ kiến thức để scale ứng dụng ngay hôm nay.
Đừng quên luôn kiểm tra log và giám sát sau khi triển khai. Nếu bạn mới bắt đầu với Nginx, hãy xem lại bài “Cấu hình Nginx Reverse Proxy” để nắm vững nền tảng trước khi load balancing.
Xem lại bài: Cấu hình Nginx Reverse Proxy