Sửa lỗi "SSL certificate problem: self-signed certificate" trong curl và HTTP Clients

beginner🔒 SSL/TLS2026-03-29| Linux, macOS, Windows — curl, Python requests, Node.js, Git, wget, bất kỳ HTTPS client nào kết nối tới server dùng self-signed TLS certificate

Error Message

SSL certificate problem: self-signed certificate
#ssl#self-signed#certificate#curl

Lỗi Gặp Phải

Bạn gọi đến một endpoint HTTPS và nhận về kết quả sau:

curl: (60) SSL certificate problem: self-signed certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it.

Server đã tự ký chứng chỉ TLS thay vì lấy từ một Tổ chức Cấp chứng chỉ (CA) uy tín. HTTP client của bạn không tìm thấy chuỗi tin cậy nào được công nhận nên từ chối kết nối. Đơn giản vậy thôi.

Nguyên Nhân

Mọi TLS client đều đi kèm danh sách các CA gốc tin cậy — chẳng hạn Let's Encrypt, DigiCert, GlobalSign. Chứng chỉ tự ký không truy ngược về bất kỳ CA nào trong số đó, nên quá trình bắt tay thất bại. Bạn thường gặp tình huống này ở một số nơi phổ biến:

  • Server nội bộ dùng cho dev hoặc staging mà không ai buồn cài chứng chỉ thật
  • Docker container, cụm Kubernetes cục bộ (kind, minikube), hoặc API riêng tư
  • Proxy doanh nghiệp chặn và giải mã traffic HTTPS rồi ký lại bằng chứng chỉ của họ
  • Server cũ vẫn đang chạy chứng chỉ đã hết hạn từ nhiều năm trước và tự gia hạn

Cách Sửa 1: Thêm Chứng Chỉ Tự Ký Vào Trust Store (Cách Đúng)

Với bất kỳ server nào bạn truy cập nhiều lần, đây là cách nên làm. Bạn tin tưởng đúng chứng chỉ cụ thể đó mà không vô hiệu hóa toàn bộ xác minh SSL.

Bước 1: Lấy chứng chỉ từ server

# Lưu chứng chỉ của server vào file
openssl s_client -connect your-server.example.com:443 -showcerts </dev/null 2>/dev/null \
  | openssl x509 -outform PEM > server-cert.pem

Bước 2: Dùng chứng chỉ với curl

curl --cacert server-cert.pem https://your-server.example.com/api/endpoint

Bước 3: Thêm vào system trust store

Làm một lần và mọi công cụ trên máy sẽ tự động tin tưởng chứng chỉ đó.

Trên Ubuntu/Debian:

sudo cp server-cert.pem /usr/local/share/ca-certificates/my-server.crt
sudo update-ca-certificates

Trên RHEL/CentOS/Fedora:

sudo cp server-cert.pem /etc/pki/ca-trust/source/anchors/my-server.crt
sudo update-ca-trust

Trên macOS:

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain server-cert.pem

Trên Windows (PowerShell chạy với quyền Admin):

Import-Certificate -FilePath .\server-cert.pem -CertStoreLocation Cert:\LocalMachine\Root

Cách Sửa 2: Bỏ Qua Xác Minh SSL (Nhanh Gọn — Chỉ Dùng Khi Dev)

Nhanh và tiện. Ổn với localhost hoặc mạng nội bộ tin cậy. Nếu bạn đưa cách này lên production, sớm muộn gì cũng sẽ có chuyện.

curl

curl -k https://your-server.example.com/api/endpoint
# hoặc dạng đầy đủ
curl --insecure https://your-server.example.com/api/endpoint

Python (thư viện requests)

import requests

# Bỏ qua xác minh — chỉ dùng khi dev, đồng thời tắt cảnh báo InsecureRequestWarning:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
response = requests.get('https://your-server.example.com/api', verify=False)

# Cách tốt hơn: trỏ trực tiếp đến file chứng chỉ
response = requests.get('https://your-server.example.com/api', verify='/path/to/server-cert.pem')

Node.js (module https)

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

// Tùy chọn 1: bỏ qua xác minh hoàn toàn (chỉ dùng khi dev)
// Đặt trước mọi lệnh gọi HTTPS
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

// Tùy chọn 2: cung cấp chứng chỉ qua custom agent (dùng được với node-fetch hoặc axios)
const agent = new https.Agent({
  ca: fs.readFileSync('/path/to/server-cert.pem')
});

// Với axios:
const axios = require('axios');
axios.get('https://your-server.example.com/api', { httpsAgent: agent });

// Với node-fetch:
const fetch = require('node-fetch');
fetch('https://your-server.example.com/api', { agent });

Git

# Clone một lần với SSL bị bỏ qua
git clone -c http.sslVerify=false https://your-server.example.com/repo.git

# Trỏ Git đến chứng chỉ của bạn (an toàn hơn)
git config --global http.sslCAInfo /path/to/server-cert.pem

# Tắt toàn cục — hoạt động nhưng để lộ bạn ở mọi nơi
git config --global http.sslVerify false

wget

wget --no-check-certificate https://your-server.example.com/file.zip

Cách Sửa 3: Thay Chứng Chỉ Tự Ký Bằng Chứng Chỉ Thật

Nếu bạn quản lý server, hãy lấy một chứng chỉ đúng nghĩa. Với các dịch vụ nội bộ không có HTTP public, DNS challenge của Let's Encrypt hoạt động rất tốt — không cần mở port 80:

# DNS challenge — không cần HTTP public
certbot certonly --manual --preferred-challenges dns -d your-internal-server.example.com

Đang chạy môi trường hoàn toàn air-gapped? Hãy dựng CA nội bộ bằng step-ca, rồi đẩy chứng chỉ gốc của nó ra toàn bộ máy qua Ansible, Group Policy, hoặc công cụ quản lý cấu hình bạn đang dùng. Mọi dịch vụ trong mạng nội bộ đều có chứng chỉ hợp lệ, không còn phiền phức với chứng chỉ tự ký nữa.

Kiểm Tra Sau Khi Sửa

# Phản hồi sạch, không có lỗi SSL
curl -v https://your-server.example.com/

# Xem chi tiết thông tin chứng chỉ
openssl s_client -connect your-server.example.com:443 </dev/null | openssl x509 -noout -text

# Xác nhận chuỗi chứng chỉ được giải quyết đúng
curl --cacert server-cert.pem -v https://your-server.example.com/ 2>&1 | grep -E "SSL|certificate|issuer"

Kết nối thành công sẽ in ra SSL connection using TLSv1.3 (hoặc TLSv1.2) trong output chi tiết, không có cảnh báo chứng chỉ nào.

Tóm Tắt Nhanh

  • Curl một lần duy nhất: dùng cờ -k rồi tiếp tục
  • Cùng server nhiều lần: thêm chứng chỉ vào system trust store một lần là xong
  • Script Python: dùng verify='/path/to/cert.pem' thay vì verify=False
  • Hệ thống production: thay chứng chỉ tự ký — không có ngoại lệ
  • Proxy doanh nghiệp chặn bạn: hỏi IT để lấy chứng chỉ CA của proxy rồi thêm vào trust store

Related Error Notes