Cơn ác mộng triển khai lúc 2 giờ sángBạn vừa đẩy một microservice mới lên môi trường production. Bên trong container, mọi thứ đều hoàn hảo; lệnh curl localhost:8080/api/health trả về 200 OK. Nhưng ngay khi bạn cố gắng truy cập qua Nginx gateway, bạn lại gặp lỗi 404 Not Found. Kỳ lạ hơn, log backend cho thấy các request đang truy cập vào các đường dẫn bị sai lệch như //user/profile hoặc /api/api/user/profile. Nginx vẫn hoạt động, upstream vẫn chạy, nhưng định tuyến thì đã hỏng.
Thông báo lỗiCác bản ghi log hoặc công cụ giám sát tự động thường báo cáo:
404 Not Found hoặc request bị chuyển hướng sai path khi dùng proxy_pass tới backend
Logic vận hành: Tại sao một dấu gạch chéo lại thay đổi tất cảNginx xử lý directive proxy_pass khác nhau dựa trên một chi tiết rất nhỏ: liệu URL có chứa URI (một đường dẫn hoặc chỉ đơn giản là một dấu gạch chéo cuối) hay không. Hành vi này là nguyên nhân của phần lớn các lỗi cấu hình reverse proxy.
Kịch bản 1: Không có URI trong proxy_pass (Chế độ "Chuyển tiếp")Khi bạn bỏ qua dấu gạch chéo cuối sau hostname hoặc port, Nginx sẽ chuyển tiếp URI request gốc chính xác như khi nó nhận được. Nó không sửa đổi chuỗi ký tự này.
location /api/ {
proxy_pass http://backend_server;
}
```- **Request đến:** `/api/users`- **Đường dẫn gửi đến Backend:** `/api/users`- **Tốt nhất cho:** Các ứng dụng Monolith hoặc backend đã được thiết lập để nhận đầy đủ prefix.### Kịch bản 2: Có URI trong proxy_pass (Chế độ "Thay thế")Nếu bạn thêm một dấu gạch chéo cuối (`/`) vào `proxy_pass`, Nginx sẽ coi đó là một sự thay thế. Nó sẽ tính toán phần URI **không** khớp với khối location và nối nó vào đường dẫn được định nghĩa trong `proxy_pass` của bạn.
location /api/ {
proxy_pass http://backend_server/;
}
```- Request đến: /api/users- Đường dẫn gửi đến Backend: /users- Lưu ý: Phần /api/ đã bị loại bỏ và được thay thế bằng dấu / được định nghĩa trong proxy_pass.## Cách khắc phục cấu hình của bạn### 1. Giữ nguyên đường dẫn đầy đủNếu dịch vụ backend của bạn (như ứng dụng Spring Boot hoặc Express) được cấu hình để lắng nghe trên /api/..., đừng sử dụng dấu gạch chéo cuối. Đây là lựa chọn mặc định an toàn nhất cho hầu hết các microservices.
location /app/ {
proxy_pass http://127.0.0.1:8080;
}
Một request đến /app/login sẽ gửi tới backend của bạn dưới dạng /app/login.
2. Loại bỏ Prefix một cách chính xácNhiều dịch vụ độc lập, như dashboard Prometheus hoặc một API chuyên dụng, mong muốn chạy ở root (/). Nếu bạn đang ánh xạ /service-a/ tới một backend không biết rằng nó đang nằm sau một proxy, bạn phải sử dụng dấu gạch chéo cuối ở cả location và proxy_pass.
location /service-a/ {
proxy_pass http://service_a_upstream/;
}
Cẩn thận với dấu gạch chéo kép: Nếu bạn sử dụng location /service-a (không có gạch chéo) với proxy_pass http://host/ (có gạch chéo), một request đến /service-a/login sẽ trở thành //login. Nhiều framework khắt khe sẽ từ chối yêu cầu này với lỗi 404 hoặc 400 Bad Request.
3. Giải pháp thay thế bằng RegexKhi logic định tuyến trở nên phức tạp, hãy sử dụng rewrite. Cách này thường dễ đọc hơn việc ghi nhớ các quy tắc về dấu gạch chéo và ngăn chặn các lỗi "lệch một ký tự" trong đường dẫn của bạn.
location /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://backend_server;
}
Các bước xác minhĐừng đoán mò, hãy đo lường thực tế. Sử dụng các công cụ sau để xem chính xác Nginx đang làm gì.
Kiểm tra Log UpstreamĐịnh nghĩa một định dạng log tùy chỉnh trong file nginx.conf của bạn. Điều này sẽ tiết lộ đường dẫn chính xác mà Nginx đang gửi đến các máy chủ backend.
log_format debug_proxy '$remote_addr - $request - to: $upstream_addr '
'uri: $uri path_sent: $request_uri';
access_log /var/log/nginx/access.log debug_proxy;
Kiểm tra với CurlSử dụng curl -Iv để kiểm tra các chuyển hướng 301. Thông thường, Nginx sẽ tự động chuyển hướng /api sang /api/, điều này có thể gây nhầm lẫn cho các ứng dụng frontend hoặc người dùng API.
curl -Iv http://your-domain.com/api/user

