Làm ngay trong 15 phút: HAProxy cơ bản
Server nginx duy nhất của công ty bắt đầu response time lên 8–12 giây vào giờ cao điểm — CPU chạm 95%, không còn chỗ để scale vertical. Thay vì nâng phần cứng, mình dựng thêm 2 backend server và đặt HAProxy ở trước. Từ lúc bắt đầu cài đến khi traffic phân tải đều, mất khoảng 15–20 phút.
Phần này giúp bạn dựng được stack tương tự từ con số 0.
Topology cần chuẩn bị
Internet
↓
[HAProxy] 192.168.1.10:80
↓ ↓
[Backend1] [Backend2]
192.168.1.11 192.168.1.12
(nginx) (nginx)
Cài đặt HAProxy
# Ubuntu/Debian
sudo apt update && sudo apt install -y haproxy
# CentOS/RHEL
sudo dnf install -y haproxy
# Kiểm tra version
haproxy -v
File cấu hình tối thiểu để chạy ngay
Sửa file /etc/haproxy/haproxy.cfg:
global
log /dev/log local0
maxconn 4096
user haproxy
group haproxy
daemon
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5s
timeout client 30s
timeout server 30s
frontend web_frontend
bind *:80
default_backend web_servers
backend web_servers
balance roundrobin
server backend1 192.168.1.11:80 check
server backend2 192.168.1.12:80 check
# Kiểm tra config trước khi restart
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Restart service
sudo systemctl restart haproxy
sudo systemctl enable haproxy
Trỏ browser vào IP của HAProxy server — request sẽ luân phiên sang backend1 và backend2. Xong phần cơ bản.
Giải thích chi tiết: Các thuật toán load balancing
Không cần biết hết — 3 thuật toán sau là đủ cho 90% trường hợp. Mình chọn cái nào phụ thuộc vào đặc tính request của từng service:
roundrobin — Mặc định, phù hợp hầu hết trường hợp
Request được phân đều theo vòng: 1→2→1→2. Tốt khi các backend server có cùng cấu hình phần cứng.
backend web_servers
balance roundrobin
server backend1 192.168.1.11:80 check weight 1
server backend2 192.168.1.12:80 check weight 1
leastconn — Dùng khi request có thời gian xử lý không đều
Request mới luôn đi đến server đang rảnh nhất — tức server có ít connection nhất tại thời điểm đó. Phù hợp cho API backend có cả endpoint upload file lẫn endpoint trả JSON nhẹ chạy chung một pool.
backend api_servers
balance leastconn
server api1 192.168.1.21:8080 check
server api2 192.168.1.22:8080 check
source — Sticky session theo IP client
Cùng một IP luôn được route đến cùng một backend. Dùng khi ứng dụng lưu session local trên từng server mà chưa migrate sang Redis.
backend legacy_app
balance source
hash-type consistent
server app1 192.168.1.31:80 check
server app2 192.168.1.32:80 check
Health check nâng cao
Mặc định, check chỉ kiểm tra TCP connection. Để kiểm tra HTTP response thực sự:
backend web_servers
balance roundrobin
option httpchk GET /health HTTP/1.1\r\nHost:\ example.com
http-check expect status 200
server backend1 192.168.1.11:80 check inter 5s rise 2 fall 3
server backend2 192.168.1.12:80 check inter 5s rise 2 fall 3
Ý nghĩa các tham số:
inter 5s— kiểm tra mỗi 5 giâyrise 2— cần 2 lần check thành công liên tiếp để server được coi là UPfall 3— 3 lần check thất bại liên tiếp → server bị đánh dấu DOWN
Mình deploy endpoint /health trả 200 OK trên mỗi backend — không phụ thuộc vào database hay cache, chỉ trả về status của chính process đó. Cách này giúp HAProxy phân biệt được backend thực sự chết với backend đang bận xử lý nặng.
Nâng cao: Stats page, SSL termination và ACL
Bật Stats Dashboard
HAProxy có built-in dashboard web — mở ra là thấy ngay từng backend đang UP hay DOWN, số connection hiện tại, error rate, response time trung bình. Không cần cài thêm gì:
frontend stats
bind *:8404
stats enable
stats uri /stats
stats refresh 10s
stats auth admin:matkhau_manh
stats hide-version
Truy cập http://192.168.1.10:8404/stats để thấy toàn bộ backend status, số kết nối, response time.
SSL Termination — Xử lý HTTPS tại HAProxy
Để HAProxy nhận HTTPS và forward HTTP đến backend (backend không cần cài SSL):
# Gộp cert và key vào 1 file .pem
cat /etc/ssl/certs/example.com.crt /etc/ssl/private/example.com.key \
> /etc/haproxy/certs/example.com.pem
chmod 600 /etc/haproxy/certs/example.com.pem
frontend web_frontend
bind *:80
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem
# Redirect HTTP → HTTPS
http-request redirect scheme https unless { ssl_fc }
# Forward header để backend biết client dùng HTTPS
http-request set-header X-Forwarded-Proto https if { ssl_fc }
default_backend web_servers
ACL — Routing theo path hoặc domain
ACL cho phép HAProxy phân loại request và route đến backend pool khác nhau. Ví dụ điển hình: /api/ đến API server, static files đến CDN cache, còn lại đến web server:
frontend web_frontend
bind *:80
acl is_api path_beg /api/
acl is_static path_end .jpg .png .css .js
use_backend api_servers if is_api
use_backend static_servers if is_static
default_backend web_servers
backend api_servers
balance leastconn
server api1 192.168.1.21:8080 check
backend static_servers
balance roundrobin
server cdn1 192.168.1.31:80 check
backend web_servers
balance roundrobin
server web1 192.168.1.11:80 check
server web2 192.168.1.12:80 check
Tips thực tế từ môi trường production
Reload config không downtime
Đây là điểm mình thích nhất ở HAProxy — reload cấu hình mà không drop connection đang có:
# Kiểm tra config trước
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
# Reload graceful (không drop connection)
sudo systemctl reload haproxy
# hoặc
sudo kill -USR2 $(cat /var/run/haproxy.pid)
Drain server trước khi maintenance
Trước khi update backend, mình luôn drain traffic thay vì tắt server thẳng tay. Dùng weight 0 qua HAProxy runtime socket:
# Kết nối vào HAProxy runtime API
echo "set weight web_servers/backend1 0" | \
sudo socat stdio /var/run/haproxy/admin.sock
# Xem trạng thái
echo "show servers state" | \
sudo socat stdio /var/run/haproxy/admin.sock
# Sau khi update xong, restore weight
echo "set weight web_servers/backend1 100" | \
sudo socat stdio /var/run/haproxy/admin.sock
Nhớ thêm dòng này vào section global — thiếu thì socat sẽ báo lỗi không kết nối được:
stats socket /var/run/haproxy/admin.sock mode 660 level admin
Log chi tiết để debug
502 từ client, không biết backend nào gây ra — đây là cách mình setup log format để trace xuống tận server cụ thể:
frontend web_frontend
bind *:80
option httplog
log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %tsc %ac/%fc/%bc/%sc/%rc %{+Q}r"
default_backend web_servers
# Xem log realtime
sudo tail -f /var/log/haproxy.log | grep "502"
Field %b/%s trong log format là tên backend và server cụ thể xử lý request đó. Chính nhờ cái này mình phát hiện backend1 đang trả 502 vì PHP-FPM bị đầy worker — trong khi backend2 vẫn hoàn toàn bình thường.
Giới hạn connection per IP để chống abuse
frontend web_frontend
bind *:80
# Giới hạn 100 connections đồng thời mỗi IP
stick-table type ip size 100k expire 30s store conn_cur
tcp-request connection track-sc1 src
tcp-request connection reject if { sc_conn_cur(1) gt 100 }
default_backend web_servers
Mình áp dụng cái này sau khi một script crawler vô tình hammer hệ thống với 500 connections đồng thời từ 1 IP — HAProxy giờ tự block trước khi request chạm đến nginx.

