Cách khắc phục nhanh (TL;DR)
Lỗi này hầu như luôn có nghĩa là Nginx đang cố gắng kết nối HTTPS với một dịch vụ backend chỉ đang lắng nghe HTTP. Cách khắc phục phổ biến nhất là thay đổi giao thức proxy_pass của bạn.
# Tìm dòng này trong cấu hình của bạn:
proxy_pass https://127.0.0.1:8080;
# Thay đổi thành thế này:
proxy_pass http://127.0.0.1:8080;
Sau khi thay đổi, hãy kiểm tra cấu hình và tải lại Nginx:
sudo nginx -t
sudo systemctl reload nginx
Nguyên nhân gốc rễ chi tiết
Thông báo lỗi SSL_do_handshake() failed (SSL: error:1408F10B:SSL routines:ssl3_get_record:wrong version number) là cách Nginx thông báo rằng nó đang bị bối rối bởi dữ liệu nhận được từ máy chủ upstream (backend).
Khi bạn sử dụng proxy_pass https://..., Nginx sẽ bắt đầu quá trình bắt tay TLS (TLS handshake). Nó gửi một gói tin "Client Hello" đến backend. Nếu backend là một máy chủ HTTP thông thường (như ứng dụng Node.js cục bộ, Gunicorn hoặc một Docker container chưa được cấu hình SSL), nó sẽ không hiểu quá trình bắt tay TLS này. Thay vì phản hồi bằng "Server Hello", backend thường gửi lại một lỗi HTTP văn bản thuần túy hoặc một phản hồi tiêu chuẩn.
Nginx cố gắng đọc vài byte đầu tiên của phản hồi đó dưới dạng số phiên bản TLS. Vì nó thấy các ký tự của "HTTP" thay vì các tiêu đề phiên bản TLS nhị phân, nó sẽ đưa ra lỗi wrong version number. Về cơ bản, đây là sự không khớp về giao thức: bạn đang nói tiếng Tây Ban Nha đã mã hóa với một người chỉ hiểu tiếng Anh thông thường.
Các kịch bản phổ biến & Cách tiếp cận khắc phục
Kịch bản 1: Vô tình sử dụng https:// trong proxy_pass
Nếu Nginx của bạn xử lý SSL (gọi là "SSL termination") và ứng dụng backend đang chạy trên cùng một máy hoặc trong mạng riêng, bạn thường không cần SSL giữa Nginx và backend. Nhiều lập trình viên sao chép cấu hình và quên thay đổi giao thức.
location /api/ {
# Lỗi xảy ra tại đây nếu backend là HTTP
proxy_pass https://backend_server;
proxy_set_header Host $host;
}
Cách khắc phục: Thay đổi https thành http. Lưu lượng nội bộ thường an toàn nếu nó đi qua local loopback hoặc một VPC bảo mật.
Kịch bản 2: Nhầm lẫn cổng (Cổng 443 so với 80)
Đôi khi bạn có thể đang trỏ đúng giao thức nhưng sai cổng, hoặc ngược lại. Nếu bạn trỏ Nginx đến https://your-backend:80, nó sẽ cố gắng thực hiện bắt tay TLS trên một cổng đang mong đợi văn bản thuần túy.
Cách khắc phục: Xác minh chính xác cổng mà backend của bạn đang lắng nghe. Nếu nó đang lắng nghe trên cổng 8080 mà không có chứng chỉ SSL, hãy sử dụng http://127.0.0.1:8080. Nếu nó thực sự phải là HTTPS, hãy đảm bảo backend đã được cấu hình các tệp .crt và .key chính xác.
Kịch bản 3: Khối Upstream không khớp
Nếu bạn sử dụng một khối upstream, hãy đảm bảo giao thức trong chỉ thị proxy_pass khớp với khả năng của các máy chủ được liệt kê trong khối đó.
upstream my_app {
server 10.0.0.5:5000;
}
server {
listen 443 ssl;
# ... cấu hình ssl ...
location / {
# Nếu các máy chủ trong 'my_app' là HTTP, dòng này phải là http://
proxy_pass http://my_app;
}
}
Các bước xác minh
Để xác nhận chính xác điều gì đang xảy ra, bạn có thể bỏ qua Nginx và giao tiếp trực tiếp với backend từ terminal của máy chủ bằng curl.
Kiểm tra xem backend có phải là HTTP không:
curl -I http://127.0.0.1:8080
Nếu lệnh này trả về mã HTTP 200 hoặc 301, backend của bạn chắc chắn là HTTP, và proxy_pass của bạn phải sử dụng http://.
Kiểm tra xem backend có phải là HTTPS không:
curl -Ik https://127.0.0.1:8080
Nếu lệnh này hoạt động (trả về các tiêu đề) nhưng kiểm tra HTTP thất bại, thì backend của bạn thực sự là HTTPS, và lỗi wrong version number có thể do phiên bản TLS quá cũ hoặc chứng chỉ bị cấu hình sai.
Phòng ngừa & Mẹo chuyên nghiệp
Khi gỡ lỗi các trường hợp không khớp giao thức này, tôi thường lưu ý một vài điều để ngăn chúng xảy ra trên môi trường production:
- **Biến môi trường:** Sử dụng biến môi trường trong các tập lệnh triển khai của bạn để thiết lập giao thức backend (`BACKEND_URL=http://...` so với `https://...`).
- **Ghi log nhất quán:** Luôn kiểm tra `/var/log/nginx/error.log`. Nó cung cấp chính xác IP và cổng upstream đã bị lỗi, điều này rất quan trọng khi bạn có nhiều upstream.
- **Tính toàn vẹn của chứng chỉ:** Nếu bạn thực sự muốn sử dụng SSL đến backend và nó thất bại, hãy xác minh rằng các tệp chứng chỉ của bạn không bị hỏng. Khi di chuyển chứng chỉ giữa các máy chủ, tôi thường sử dụng [Trình tạo mã băm từ ToolCraft](https://toolcraft.app/en/tools/developer/hash-generator) để tạo mã băm SHA-256 của tệp `.crt`. Việc so sánh mã băm ở nguồn và đích đảm bảo tệp không bị cắt bớt hoặc bị lỗi trong quá trình truyền qua `scp` hoặc `rsync`.
Đọc thêm
- Tài liệu Nginx về [proxy_pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass)
- Hiểu về SSL Termination so với End-to-End Encryption
- Gỡ lỗi Nginx Upstream errors

