Cách khắc phục lỗi Nginx SSL_do_handshake() Failed (error:14090086) cho HTTPS Upstream

intermediate🔒 SSL/TLS2026-07-11| Nginx (1.10+), Linux (Ubuntu 20.04+, CentOS 7+), Docker, Kubernetes Ingress

Error Message

SSL_do_handshake() failed (SSL: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed) while SSL handshaking to upstream
#nginx#ssl#reverse-proxy#devops#troubleshooting

Vấn đề

Nếu bạn đã từng kiểm tra error.log của Nginx và thấy một lỗi handshake khó hiểu, có khả năng bạn đang gặp vấn đề về độ tin cậy của chứng chỉ. Lỗi cụ thể này thường xuất hiện khi Nginx đóng vai trò là reverse proxy cho một máy chủ backend yêu cầu HTTPS. Mục nhật ký thường trông như thế này:

[error] 12345#0: *678 SSL_do_handshake() failed (SSL: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed) while SSL handshaking to upstream, client: 1.2.3.4, server: example.com, request: "GET /api HTTP/1.1", upstream: "https://10.0.0.5:443/api", host: "example.com"

Gốc rễ của vấn đề là Nginx hoạt động giống như một trình duyệt web khi giao tiếp với backend (upstream) của bạn. Nếu bạn yêu cầu Nginx xác minh danh tính của backend nhưng nó không thể xác thực chứng chỉ so với danh sách các tổ chức phát hành (CA) đáng tin cậy, nó sẽ ngắt kết nối ngay lập tức để giữ an toàn cho dữ liệu.

Tóm tắt: Cách khắc phục nhanh

Đối với môi trường phát triển cục bộ hoặc kiểm thử nội bộ nơi bảo mật không phải là ưu tiên hàng đầu, bạn có thể yêu cầu Nginx bớt khắt khe hơn. Thêm proxy_ssl_verify off; vào khối location của bạn sẽ bỏ qua bước kiểm tra này:

location / {
    proxy_pass https://backend_upstream;
    proxy_ssl_verify off;
}

Cảnh báo: Đừng bao giờ sử dụng cách này trong môi trường production. Việc bỏ qua xác thực SSL khiến lưu lượng nội bộ của bạn dễ bị tấn công man-in-the-middle (người đứng giữa).

Tại sao Handshake thất bại

- **Chứng chỉ không đáng tin cậy:** Backend của bạn sử dụng chứng chỉ tự ký (self-signed) hoặc một CA nội bộ mà Nginx không nhận diện được.
- **Thiếu chuỗi CA:** Nginx thiếu gói Root CA cụ thể cần thiết để xác minh toàn bộ đường dẫn chứng chỉ.
- **Không khớp SNI:** Các backend hiện đại—đặc biệt là trên AWS, Cloudflare, hoặc Kubernetes—yêu cầu Server Name Indication (SNI). Nếu Nginx không gửi hostname, backend sẽ cung cấp sai chứng chỉ.
- **Chứng chỉ hết hạn:** Chứng chỉ backend đã quá hạn, hoặc đồng hồ hệ thống trên máy chủ Nginx của bạn bị lệch.

Các bước khắc phục chi tiết

Cách 1: Tin cậy chứng chỉ Backend (Khuyên dùng)

Nếu backend của bạn sử dụng CA nội bộ, Nginx cần thấy thông tin xác thực đó. Trước tiên, hãy xuất chứng chỉ CA của backend dưới dạng tệp .crt hoặc .pem. Sau đó, chỉ định rõ ràng cho Nginx tin cậy tệp đó.

location / {
    proxy_pass https://backend_server;
    proxy_ssl_trusted_certificate /etc/nginx/certs/internal-ca.crt;
    proxy_ssl_verify on;
    proxy_ssl_verify_depth 2;
}

Cách 2: Bật hỗ trợ SNI

Theo mặc định, Nginx không truyền hostname trong quá trình SSL handshake với backend. Điều này thường gây ra lỗi khi proxy đến các dịch vụ như AWS Application Load Balancer (ALB) hoặc Kubernetes Ingress controller. Bạn có thể khắc phục bằng cách bật proxy_ssl_server_name.

location / {
    proxy_pass https://api.internal.service;
    # Dòng này gửi hostname đến backend trong quá trình handshake
    proxy_ssl_server_name on;
    proxy_ssl_name api.internal.service;
}

Cách 3: Cập nhật kho lưu trữ CA của hệ thống

Đôi khi vấn đề không nằm ở cấu hình; mà ở hệ điều hành của bạn. Nếu backend sử dụng chứng chỉ tiêu chuẩn (như Let's Encrypt) nhưng máy chủ Nginx chạy hệ điều hành cũ, các chứng chỉ gốc có thể đã hết hạn. Điều này từng xảy ra khá phổ biến với sự kiện hết hạn DST Root CA X3 vào năm 2021.

# Cho Ubuntu 22.04 hoặc Debian
sudo apt-get update && sudo apt-get install --only-upgrade ca-certificates

# Cho CentOS 7 hoặc RHEL 8
sudo yum update ca-certificates

Xác minh kết quả

Đừng chỉ khởi động lại Nginx và hy vọng mọi thứ ổn. Luôn kiểm tra cú pháp trước để tránh làm gián đoạn dịch vụ:

nginx -t

Nếu cú pháp hợp lệ, hãy tải lại dịch vụ để áp dụng các thay đổi mà không làm ngắt các kết nối đang hoạt động:

systemctl reload nginx

Theo dõi nhật ký bằng lệnh tail -f /var/log/nginx/error.log trong khi bạn tải lại trang trình duyệt. Bạn sẽ thấy các lỗi 14090086 biến mất, thay vào đó là các mục 200 OK thành công trong access log.

Mẹo chuyên gia: Kiểm tra tính toàn vẹn của tệp

Khi di chuyển các tệp chứng chỉ giữa các máy chủ qua SCP hoặc sao chép-dán, tệp đôi khi có thể bị hỏng hoặc bị cắt bớt. Chỉ cần một ký tự bị thiếu trong tệp PEM sẽ làm hỏng toàn bộ quá trình handshake.

Tôi khuyên bạn nên xác minh tính toàn vẹn của các tệp .crt bằng cách so sánh mã hash. Sử dụng một công cụ như Hash Generator tại ToolCraft cho phép bạn tạo mã SHA-256 cục bộ ngay trong trình duyệt. Nếu mã hash trên máy cục bộ khớp với mã hash của tệp trên máy chủ Nginx, bạn có thể chắc chắn 100% rằng tệp còn nguyên vẹn. Điều này giúp tiết kiệm hàng giờ gỡ lỗi cho các vấn đề cấu hình "ma".

Related Error Notes