Trong mô hình nhiều website trên Cloud VPS, mọi request đều đi qua một cửa: tầng balancer. Đây là các máy duy nhất có IP public, chạy Nginx reverse proxy để giải mã HTTPS, chặn request rác rồi chuyển phần còn lại về đúng minion trong mạng private. Tài liệu này trình bày cách tổ chức cấu hình trên balancer: một file cho mỗi domain, các header proxy bắt buộc, timeout, giới hạn upload, gzip, chuyển HTTP sang HTTPS, cách đọc lỗi 502, 504, 413 và checklist trước khi đưa một site lên.
1. Nginx reverse proxy làm gì ở tầng balancer
Reverse proxy là máy chủ đứng trước ứng dụng, nhận request thay cho ứng dụng rồi chuyển tiếp vào trong. Trình duyệt chỉ nói chuyện với balancer, không bao giờ thấy minion. Trong mô hình Balancer – Minion – Database, Nginx reverse proxy trên balancer gánh bốn việc:
- SSL offload: bắt tay TLS, giữ chứng chỉ, giải mã. Minion phía sau chỉ nhận HTTP thuần qua mạng private.
- Định tuyến domain: dựa vào
server_nameđể biết domain nào đi về minion nào. - Chặn sớm: trả 403 cho đường dẫn nhạy cảm, giới hạn tốc độ request trước khi chúng tốn worker PHP.
- Nén và chuẩn hoá: gzip, ẩn phiên bản nginx, ép HTTPS.
Khi mỗi domain chỉ trỏ về một minion, Nginx reverse proxy trên balancer là cổng vào và bộ định tuyến, chưa phải bộ cân bằng tải giữa nhiều máy.
2. Cấu trúc thư mục Nginx reverse proxy: một file cho mỗi domain
/etc/nginx/
├── nginx.conf # cấu hình chung: worker, gzip, zone rate limit
├── conf.d/
│ ├── example.com.conf # upstream + server :80 + server :443
│ ├── site-a.example.conf
│ └── ...
└── snippets/
└── security-blocking.conf # luật chặn dùng chung, include vào từng site
/etc/letsencrypt/live/<domain>/ # chứng chỉ do certbot cấpNguyên tắc: một domain, một file, tên file chính là domain. Thêm site là tạo một file, gỡ site là xoá một file, chuyển site là sửa một dòng. Muốn biết domain nằm trên balancer nào, chỉ cần ls /etc/nginx/conf.d/.
Script có thể gọi minion bằng tên trong DNS nội bộ, nhưng file cấu hình Nginx reverse proxy nên ghi IP private: nginx chỉ phân giải tên trong upstream một lần lúc nạp cấu hình, ghi IP giúp balancer không phụ thuộc DNS khi khởi động.
Một chi tiết hay bị bỏ qua: nginx.conf chỉ nạp conf.d/*.conf. Bản sao lưu nên đặt đuôi như example.com.conf.bak để không bị nạp. Nếu đặt là example.com.bak.conf, nginx sẽ nạp cả hai và báo trùng server_name, hoặc tệ hơn là dùng nhầm bản cũ.
3. Cấu hình chung trong nginx.conf
Phần cấu hình chung quyết định sức chịu tải của cả Nginx reverse proxy và áp dụng cho mọi domain trên balancer. Cấu hình mẫu:
worker_processes auto; # một worker cho mỗi vCPU
worker_rlimit_nofile 65535; # mỗi kết nối proxy tốn 2 file descriptor
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
server_tokens off; # không lộ phiên bản nginx trong header và trang lỗi
client_max_body_size 64M; # giới hạn upload mặc định cho mọi site
gzip on;
gzip_comp_level 6;
gzip_proxied any;
gzip_types text/css application/javascript application/json image/svg+xml text/xml;
limit_req_zone $binary_remote_addr zone=dynamic:10m rate=25r/s;
limit_req_status 429; # mặc định là 503, dễ nhầm với lỗi hệ thống
include /etc/nginx/conf.d/*.conf;
}worker_processes autovớiepolllà lựa chọn chuẩn trên Linux. Trên balancer một vCPU chỉ có một worker, nên tổng số kết nối đồng thời gần bằngworker_connections. Mỗi request proxy tốn hai kết nối (client và minion), nên số người dùng thực tế chỉ khoảng một nửa.gzip_proxied anycho phép Nginx reverse proxy nén cả response từ minion. Không cần thêm JPEG, PNG vàogzip_typesvì chúng đã được nén sẵn.
4. File cấu hình một site trên Nginx reverse proxy
upstream example_com {
server 10.0.0.21:80;
}
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
add_header Strict-Transport-Security "max-age=63072000" always;
access_log /var/log/nginx/example.com.access;
error_log /var/log/nginx/example.com.error;
include snippets/security-blocking.conf;
location / {
limit_req zone=dynamic burst=50;
proxy_pass http://example_com;
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;
proxy_connect_timeout 10s;
proxy_read_timeout 300s;
}
}Bốn header proxy không được thiếu
Khi làm Nginx reverse proxy, nginx tạo một request mới tới minion chứ không chuyển nguyên request gốc. Những thông tin sau phải được gửi kèm một cách tường minh:
Host: mặc định nginx gửi tên upstream (example_com) chứ không phải domain. Minion dùngserver_nameđể chọn site, nên thiếu dòng này thì request rơi vào site mặc định của minion.X-Real-IPvàX-Forwarded-For: IP thật của người dùng. Thiếu chúng, minion và ứng dụng chỉ thấy IP của balancer, log và mọi giới hạn theo IP ở phía sau đều sai.X-Forwarded-Proto: cho ứng dụng biết request gốc là HTTPS. Laravel cần header này (cùng cấu hình TrustProxies) để sinh linkhttps://.
Timeout: lỗi nên xảy ra ở đâu
Mặc định, Nginx reverse proxy chờ minion trả lời tối đa 60 giây (proxy_read_timeout). Trang quản trị xuất báo cáo hay import file có thể chạy lâu hơn, khi đó có thể nâng lên vài phút. Quy tắc: timeout ở balancer luôn lớn hơn timeout ở minion (fastcgi_read_timeout) và giới hạn thời gian của PHP. Request chậm bị cắt ở tầng gần nguyên nhân nhất, log lỗi nằm ở minion và dễ lần ra script nào chậm. proxy_connect_timeout thì ngược lại, nên ngắn: trong mạng private, vài giây không kết nối được nghĩa là minion có vấn đề, chờ thêm cũng không giúp gì.
HTTP sang HTTPS và HSTS
Khối listen 80 chỉ làm một việc là return 301 sang HTTPS. Header HSTS bảo trình duyệt chỉ dùng HTTPS cho domain này trong khoảng max-age (ví dụ trên là hai năm). Hai lưu ý:
- Chỉ thêm
includeSubDomainskhi chắc chắn mọi subdomain đều có HTTPS. Nếu không, subdomain chỉ chạy HTTP sẽ không mở được trên trình duyệt đã ghi nhớ HSTS. add_headerchỉ được kế thừa khi khối con không cóadd_headernào. Nếu tronglocationcó thêm mộtadd_headerkhác, header HSTS ở cấpserversẽ biến mất khỏi response của location đó.
5. Snippet chặn cơ bản và rate limit
Một snippet dùng chung, include vào mọi site, chặn ngay tại Nginx reverse proxy các đường dẫn không bao giờ hợp lệ với website Laravel:
# /etc/nginx/snippets/security-blocking.conf
location ~ /\.(env|git|svn|htaccess) { deny all; }
location ~* \.(sql|bak|swp)$ { deny all; }
location ~* (wp-login\.php|xmlrpc\.php|phpmyadmin) { return 403; }Các location dạng regex được nginx ưu tiên hơn location /, nên request /.env bị trả 403 ngay ở balancer, không chạm tới minion. Website Laravel không có wp-login.php, nên mọi request tới đó đều là bot dò lỗ hổng.
Rate limit dùng module ngx_http_limit_req_module. Trong ví dụ, rate=25r/s là tốc độ trung bình cho mỗi IP, burst=50 là số request được xếp hàng khi vượt tốc độ đó; request vượt cả burst nhận mã 429. Rate limit ở đây đếm mọi request, kể cả CSS, JS, ảnh. Trang có vài chục tài nguyên tĩnh có thể chạm ngưỡng nếu burst quá nhỏ, nên hãy thử trên trang nặng nhất trước khi siết.
6. Quy trình sửa cấu hình: luôn nginx -t trước
Mọi thay đổi trên Nginx reverse proxy nên kết thúc bằng đúng một lệnh:
nginx -t && systemctl reload nginxKhông dùng restart trên Nginx reverse proxy khi chỉ sửa cấu hình. reload để master process đọc cấu hình mới, khởi động worker mới và cho worker cũ phục vụ nốt các kết nối đang mở, người dùng không bị ngắt. Quan trọng hơn: nếu file mới lỗi cú pháp, nginx -t dừng ngay và cấu hình cũ vẫn chạy. Một balancer phục vụ hàng chục domain, thiếu một dấu chấm phẩy mà dùng restart là tất cả cùng ngừng.
7. Lỗi thường gặp trên Nginx reverse proxy
| Triệu chứng | Dòng log ở balancer | Nguyên nhân thường gặp |
|---|---|---|
| 502 Bad Gateway ngay lập tức | connect() failed (111: Connection refused) while connecting to upstream | Nginx trên minion chết, sai IP upstream, hoặc minion không listen trên IP private |
| 502 sau vài giây | upstream prematurely closed connection | PHP-FPM trên minion chết hoặc worker bị kill giữa chừng |
| 504 Gateway Timeout | upstream timed out (110: Connection timed out) | Minion quá tải, query chậm, hoặc minion không phản hồi |
| 413 Request Entity Too Large | client intended to send too large body | File upload lớn hơn client_max_body_size |
| Trình duyệt báo "too many redirects" | Không có lỗi, chỉ thấy chuỗi 301 | Ứng dụng không biết request là HTTPS nên redirect ngược lại |
Cách phân biệt nhanh trên Nginx reverse proxy: 502 là không nói chuyện được với minion, 504 là minion nhận rồi nhưng trả lời quá chậm. Với 502, kiểm tra systemctl status nginx và PHP-FPM trên minion. Với 504, tìm request chậm ở log minion và log ứng dụng.
Lỗi 413 cần sửa ở ba nơi, vì giới hạn nhỏ nhất sẽ thắng: client_max_body_size trên balancer, client_max_body_size trên nginx của minion, và upload_max_filesize, post_max_size trong PHP. Nâng ở Nginx reverse proxy mà quên hai nơi kia, lỗi chỉ đổi tầng chứ không hết.
Vòng redirect hầu như luôn do ứng dụng tưởng request là HTTP. Kiểm tra header X-Forwarded-Proto do Nginx reverse proxy gửi, cấu hình tin proxy của ứng dụng và fastcgi_param HTTPS on trên minion. Nếu domain đi qua Cloudflare với chế độ SSL "Flexible", Cloudflare gọi balancer bằng HTTP, balancer lại redirect sang HTTPS và vòng lặp xảy ra ngay ở tầng ngoài cùng. Chế độ đúng khi balancer đã có chứng chỉ là "Full (strict)".
8. Checklist cấu hình Nginx reverse proxy cho một site mới
- File đặt tên theo domain trong
conf.d/, không để bản sao lưu có đuôi.conf. - Upstream ghi IP private của minion; minion chỉ listen trên IP private.
- Đủ bốn header
Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto. - Khối port 80 chỉ redirect 301; HSTS chỉ bật
includeSubDomainskhi chắc chắn. - Timeout balancer lớn hơn timeout minion;
client_max_body_sizekhớp với PHP. - Đã include snippet chặn dùng chung và zone rate limit.
- Log riêng cho domain, có logrotate để không đầy ổ đĩa.
- Chạy
nginx -trồi mớireload, sau đócurl -I https://example.comkiểm tra.
Phần lớn hành vi mặc định gây lỗi khó hiểu (header Host, timeout 60 giây, buffer) đều được mô tả trong tài liệu ngx_http_proxy_module. Cấu hình Nginx reverse proxy không phức tạp; cái khó là giữ cho hàng chục file cấu hình site cùng theo một khuôn, để người trực đọc file nào cũng hiểu ngay nó làm gì, và dựng lại balancer nhanh khi có sự cố.
Bài tiếp theo
Tầng Minion: chạy nhiều phiên bản PHP song song. Cài PHP 7.2, 7.4 và 8.x từ cùng một nguồn gói, socket PHP-FPM theo phiên bản, quy ước thư mục site và supervisor cho queue worker.
