Bất ngờ sau khi triển khai
Bạn vừa hoàn tất việc triển khai SSL suôn sẻ hoặc di chuyển ứng dụng của mình ra sau một bộ cân bằng tải mới. Mọi thứ dường như hoàn hảo cho đến khi bạn kiểm tra nhật ký (logs) và nhận thấy một sự gia tăng đột biến các lỗi 400. Khi người dùng vô tình nhập http:// thay vì https:// trong khi chỉ định cổng 443, Nginx sẽ gửi cho họ một thông báo lỗi thẳng thừng:
400 Bad Request: The plain HTTP request was sent to HTTPS port
Điều này xảy ra vì Nginx đang chờ đợi một cách nghiêm ngặt một quá trình bắt tay SSL/TLS đã mã hóa trên cổng 443. Nếu nó nhận được một yêu cầu GET tiêu chuẩn, không mã hóa, nó sẽ không biết cách xử lý tiếp theo. Thay vì đoán ý định của bạn, Nginx sẽ ngắt kết nối để bảo vệ socket bảo mật.
Cách khắc phục trong 30 giây
Để khắc phục điều này, bạn có thể hướng dẫn Nginx chặn lỗi cụ thể này và chuyển hướng người dùng đến URL bảo mật chính xác. Thêm dòng này vào bên trong khối server xử lý lưu lượng SSL của bạn:
server {
listen 443 ssl;
server_name example.com;
# Chuyển hướng lỗi nội bộ 497 sang phiên bản HTTPS
error_page 497 https://$host$request_uri;
# ... cấu hình SSL còn lại của bạn
}
Sau khi lưu tệp, hãy áp dụng các thay đổi bằng lệnh nginx -s reload. Nginx sử dụng mã nội bộ 497 cụ thể cho các trường hợp không khớp cổng HTTP-sang-HTTPS. Chỉ thị này sẽ bắt sự kiện đó và thực hiện chuyển hướng kiểu 301 sang giao thức bảo mật.
Tại sao lỗi này lại xảy ra?
Sự nghiêm ngặt của giao thức là vấn đề cốt lõi. Khi bạn định nghĩa listen 443 ssl;, Nginx mong đợi byte đầu tiên của kết nối là điểm bắt đầu của một quá trình bắt tay TLS. Nếu một script cũ hoặc một mục nhập URL thủ công gửi GET / HTTP/1.1 dưới dạng văn bản thuần túy tới cổng đó, Nginx sẽ coi đó là dữ liệu rác. Nó không thể nâng cấp kết nối giữa chừng, vì vậy nó kích hoạt phản hồi 400.
Bạn thường sẽ thấy điều này trong ba trường hợp cụ thể:
- **Lỗi nhập URL thủ công:** Người dùng nhập rõ ràng `http://yourdomain.com:443` vào trình duyệt của họ.
- **Sự không khớp ở bộ cân bằng tải:** Một dịch vụ thượng nguồn như AWS ALB hoặc HAProxy được cấu hình để giao tiếp với Nginx qua HTTP thuần túy nhưng lại nhắm mục tiêu vào cổng đã bật SSL của Nginx.
- **Các API client bị gán cứng (hardcoded):** Các ứng dụng di động cũ hoặc thiết bị IoT có thể đã gán cứng `http://` khi cố gắng truy cập một dịch vụ vừa mới chuyển sang cổng 443.
Các phương pháp cấu hình nâng cao
Phương pháp 1: Chỉ thị error_page 497 (Cách tốt nhất)
Đây là cách tiếp cận hiệu quả nhất vì nó xử lý lỗi tại nguồn. Nó ngăn kết nối bị ngắt và giữ nguyên đường dẫn cụ thể cũng như các chuỗi truy vấn (query strings) của người dùng.
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# Xử lý lỗi không khớp giữa văn bản thuần túy và cổng SSL
error_page 497 https://$host$request_uri;
location / {
proxy_pass http://localhost:8080;
}
}
Phương pháp 2: Giải quyết vòng lặp Proxy và Load Balancer
Nếu thực thể Nginx của bạn nằm sau một bộ cân bằng tải (Load Balancer) thực hiện SSL Termination, thiết lập sẽ thay đổi. Trong trường hợp này, Load Balancer xử lý chứng chỉ và giao tiếp với Nginx qua HTTP thuần túy. Nếu Nginx vẫn bật ssl trên cổng lắng nghe của nó, nó sẽ từ chối lưu lượng từ Load Balancer.
Giải pháp: Loại bỏ chỉ thị ssl khỏi Nginx và dựa vào header X-Forwarded-Proto để xác định xem yêu cầu ban đầu có an toàn hay không.
server {
listen 80; # LB giao tiếp với Nginx qua cổng 80
server_name example.com;
# Tin cậy dải IP nội bộ của Load Balancer của bạn (ví dụ: 10.0.0.0/8)
real_ip_header X-Forwarded-For;
set_real_ip_from 10.0.0.0/8;
location / {
proxy_pass http://app_backend;
}
}
Kiểm tra giải pháp
Bộ nhớ đệm của trình duyệt có thể gây nhầm lẫn khi kiểm tra chuyển hướng. Hãy sử dụng curl trong terminal của bạn để xác minh rằng Nginx đang phản hồi chính xác với sự không khớp này.
1. Mô phỏng điều kiện lỗi:
curl -I http://yourdomain.com:443
Trước khi khắc phục, lệnh này sẽ trả về HTTP/1.1 400 Bad Request. Sau khi áp dụng Phương pháp 1, bạn sẽ thấy mã 301 Moved Permanently hoặc 302 Found trỏ đến phiên bản https:// của trang web.
2. Kiểm tra cú pháp Nginx:
nginx -t
Luôn chạy kiểm tra này. Chỉ một dấu chấm phẩy bị thiếu cũng có thể làm sập toàn bộ máy chủ web của bạn trong quá trình tải lại (reload).
Các lỗi thường gặp với Docker và mạng
- **Ánh xạ cổng Docker:** Nếu lệnh chạy Docker của bạn sử dụng `-p 443:80`, bạn đang ánh xạ lưu lượng SSL bên ngoài vào một cổng không phải SSL bên trong. Nếu Nginx bên trong container đó đang mong đợi SSL trên cổng 80, sự không khớp là không thể tránh khỏi. Hãy giữ logic cổng nội bộ và ngoại vi của bạn nhất quán.
- **Tác động của HSTS:** Strict Transport Security (HSTS) thường ngăn trình duyệt mắc phải lỗi này. Tuy nhiên, nó sẽ không giúp ích gì cho `curl`, Postman hoặc các cuộc gọi API từ backend-sang-backend.
- **Kiểm tra của tường lửa:** Một số tường lửa doanh nghiệp thực hiện kiểm tra gói tin sâu (Deep Packet Inspection - DPI). Nếu chúng tước bỏ hoặc sửa đổi quá trình bắt tay TLS, Nginx có thể coi dữ liệu đến là văn bản thuần túy.

