Khắc phục lỗi DEPTH_ZERO_SELF_SIGNED_CERT trong Node.js (Hướng dẫn SSL/TLS)

intermediate🔒 SSL/TLS2026-05-01| Node.js (v14+), Linux/macOS/Windows, Axios, Node Fetch, hoặc module HTTPS mặc định

Error Message

Error: self signed certificate (DEPTH_ZERO_SELF_SIGNED_CERT)
#nodejs#https#ssl#chứng chỉ tự ký#fetch#axios

Bối cảnh

Tôi vừa gặp phải một trở ngại lớn khi kết nối một microservice với hệ thống kiểm kê nội bộ. API kế thừa này nằm trên một máy chủ cục bộ tại 10.0.0.50:8443 và sử dụng chứng chỉ SSL tự ký (self-signed). Trong khi trình duyệt cho phép bạn nhấp vào "Nâng cao" (Advanced) và "Tiếp tục" (Proceed anyway), Node.js lại khắt khe hơn nhiều. Nó luôn ưu tiên bảo mật hơn là sự tiện lợi.

Ngay khi yêu cầu Axios của tôi gửi đến endpoint nội bộ đó, ứng dụng đã bị dừng. Thông báo lỗi này là thứ duy nhất còn lại:

Error: self signed certificate (DEPTH_ZERO_SELF_SIGNED_CERT)

Node.js dựa vào một danh sách cố định các Tổ chức phát hành chứng chỉ (Certificate Authorities - CAs) đáng tin cậy. Khi bạn truy cập vào một máy chủ có chứng chỉ không được ký bởi một đơn vị lớn như Let's Encrypt, Node.js sẽ ngắt kết nối. Nó giả định rằng bạn đang bị tấn công xen giữa (man-in-the-middle). Điều này rất quan trọng cho môi trường production nhưng lại là một trở ngại lớn cho việc phát triển nội bộ.

Quy trình Debug

Mã lỗi DEPTH_ZERO_SELF_SIGNED_CERT chính là bằng chứng xác thực. Nó có nghĩa là chứng chỉ ở tầng dưới cùng của chuỗi (độ sâu bằng 0) là chứng chỉ tự ký. Không có Root CA nào để xác minh rằng máy chủ đúng như những gì nó tuyên bố.

Để loại trừ lỗi cấu hình từ phía máy chủ, tôi đã kiểm tra endpoint bằng curl:

curl -I https://internal-api.local/data

Kết quả trả về đã xác nhận nghi ngờ của tôi. Curl đưa ra cảnh báo "chứng chỉ không đáng tin cậy" tương tự. Tuy nhiên, khi chạy curl -k thì lại hoạt động hoàn hảo. Điều này chứng minh rằng máy chủ vẫn ổn; vấn đề hoàn toàn nằm ở cách client Node.js của tôi xử lý việc xác thực.

Các giải pháp

Bạn có ba cách để xử lý vấn đề này, từ một giải pháp tình thế kiểu "tùy chọn hạt nhân" cho đến các cấu hình sẵn sàng cho production.

1. "Tùy chọn hạt nhân" (Chỉ dùng khi phát triển)

Nếu bạn cần triển khai nhanh và đang làm việc hoàn toàn trong môi trường local biệt lập, bạn có thể ép Node.js bỏ qua tất cả các lỗi SSL. Điều này được xử lý thông qua một biến môi trường.

Tùy chọn A: Thiết lập trong mã nguồn

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

// Your Axios/Fetch code follows
axios.get('https://internal-api.local/data');

Tùy chọn B: Thiết lập qua terminal

export NODE_TLS_REJECT_UNAUTHORIZED=0
node app.js

CẢNH BÁO: Hãy sử dụng tùy chọn này một cách cực kỳ thận trọng. Nó sẽ vô hiệu hóa việc xác thực SSL cho mọi yêu cầu. Ứng dụng của bạn sẽ không còn xác minh các kết nối đến Stripe, AWS hoặc bất kỳ API bên ngoài nào khác, khiến bạn dễ bị đánh cắp dữ liệu.

2. Phương pháp tiếp cận chính xác (Dành riêng cho Client)

Một cách tốt hơn là yêu cầu HTTP client của bạn tin tưởng một chứng chỉ cụ thể. Bạn vẫn giữ nguyên tính bảo mật toàn cục trong khi tạo ra một ngoại lệ duy nhất cho máy chủ nội bộ của mình.

Sử dụng Axios với một tệp chứng chỉ:

const https = require('https');
const fs = require('fs');

const agent = new https.Agent({
  ca: fs.readFileSync('./certs/internal-server.pem')
});

axios.get('https://internal-api.local/data', { httpsAgent: agent });

Nếu bạn không có tệp .pem nhưng vẫn muốn giới hạn rủi ro cho một instance client duy nhất, bạn có thể vô hiệu hóa xác thực chỉ cho agent đó:

const agent = new https.Agent({
  rejectUnauthorized: false
});

axios.get('https://internal-api.local/data', { httpsAgent: agent });

3. Tiêu chuẩn doanh nghiệp: NODE_EXTRA_CA_CERTS

Trong môi trường công ty, có khả năng bạn có một Root CA nội bộ. Thay vì sửa đổi mọi https.Agent, bạn có thể yêu cầu Node.js mở rộng danh sách các cơ quan được tin tưởng trên toàn cầu.

export NODE_EXTRA_CA_CERTS="/path/to/company-root-ca.pem"
node app.js

Đây là phương pháp sạch nhất cho các đội ngũ phát triển. Nó cho phép Node.js tin tưởng cơ sở hạ tầng nội bộ của bạn trong khi vẫn duy trì các bước kiểm tra bảo mật tiêu chuẩn cho phần còn lại của internet.

Các bước xác minh

Đừng vội cho rằng lỗi đã được khắc phục chỉ vì thông báo lỗi không còn nữa. Hãy làm theo các bước sau để đảm bảo bạn không tạo ra lỗ hổng bảo mật:

  • Bước 1: Xóa biến NODE_TLS_REJECT_UNAUTHORIZED khỏi môi trường của bạn.
  • Bước 2: Áp dụng bản sửa lỗi https.Agent bằng thuộc tính ca.
  • Bước 3: Xác nhận ứng dụng của bạn có thể lấy dữ liệu từ API nội bộ.
  • Bước 4 (Bài kiểm tra quyết định): Thử kết nối với một trang HTTPS nội bộ khác mà bạn chưa thêm vào. Nó vẫn sẽ thất bại. Điều này chứng minh rằng bộ lọc bảo mật của bạn vẫn đang hoạt động.

Bài học kinh nghiệm

Lỗi này không phải là một bug; đó là Node.js đang thực hiện đúng chức năng của nó. Những điểm mấu chốt tôi rút ra được từ phiên debug này:

  • Các biến môi trường vẫn tồn tại: Rất dễ để thiết lập NODE_TLS_REJECT_UNAUTHORIZED=0 trong một phiên terminal rồi quên béng mất. Điều này có thể dẫn đến việc triển khai mã nguồn không an toàn.
  • Sắp xếp là quan trọng: Hãy lưu trữ các chứng chỉ nội bộ của bạn trong một thư mục /certs riêng biệt. Chỉ cần nhớ thêm chúng vào tệp .gitignore.
  • Hãy can thiệp chính xác: Luôn ưu tiên dùng https.Agent hoặc NODE_EXTRA_CA_CERTS thay vì các cờ (flags) toàn cục. Tính bảo mật cao và năng suất của lập trình viên không nhất thiết phải loại trừ lẫn nhau.

Related Error Notes