cURL 存取 HTTPS API 時常遇到「SSL certificate problem: certificate verify failed」錯誤。本文分析憑證驗證機制、提供開發/生產環境差異化解決方案、說明安全性風險與正確部署流程。
SSL certificate problem, verify that the CA cert is OK. Details: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
<?php
$ch = curl_init('https://api.example.com/endpoint');
// ⚠️ 關閉對等憑證驗證 (不驗證伺服器身份)
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 仍檢查 SSL 加密演算法是否存在 (保留最基本防護)
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 2 = 檢查 CN 是否存在
// ... 其他設定
$response = curl_exec($ch);
?>
# 方式 A:從系統套件安裝 (推薦) # CentOS/RHEL yum install ca-certificates # Ubuntu/Debian apt-get install ca-certificates # 方式 B:使用 Mozilla 官方 CA 包 (cURL 官方推薦) wget https://curl.se/ca/cacert.pem -O /etc/ssl/certs/cacert.pem
<?php
$ch = curl_init('https://api.example.com/endpoint');
// 指定 CA 憑證檔案路徑
curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/cacert.pem');
// 或指定 CA 目錄 (需經 c_rehash 處理)
curl_setopt($ch, CURLOPT_CAPATH, '/etc/ssl/certs/');
// 正常驗證模式
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
; php.ini [curl] ; 指定預設 CA 檔案 curl.cainfo = "/etc/ssl/certs/cacert.pem" [openssl] ; OpenSSL 擴展預設 CA openssl.cafile = "/etc/ssl/certs/cacert.pem"
<?php
/**
* 安全的 HTTPS 請求封裝
* @param string $url 請求 URL
* @param array $data POST 資料
* @param array $header 自訂標頭
* @param int $timeout 逾時秒數
* @return string|false 回應內容或 false
*/
function curl_https($url, array $data = [], array $header = [], $timeout = 30) {
$ch = curl_init();
// 基礎設定
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_POST => !empty($data),
CURLOPT_POSTFIELDS => $data ? http_build_query($data) : '',
CURLOPT_HTTPHEADER => $header ?: ['Content-Type: application/x-www-form-urlencoded'],
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
// SSL 安全設定 (生產環境標準)
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
// CURLOPT_CAINFO => '/path/to/cacert.pem', // 若 php.ini 未設定則需指定
]);
// 執行請求
$response = curl_exec($ch);
// 錯誤處理
if ($response === false) {
$errorNo = curl_errno($ch);
$errorMsg = curl_error($ch);
error_log("cURL 錯誤 [$errorNo]: $errorMsg | URL: $url");
curl_close($ch);
return false;
}
// 檢查 HTTP 狀態碼
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode < 200 || $httpCode >= 300) {
error_log("HTTP 錯誤 [$httpCode]: $url");
}
curl_close($ch);
return $response;
}
// 使用範例
$response = curl_https('https://api.example.com/v1/users', [
'action' => 'create',
'name' => 'John Doe'
], [
'Authorization: Bearer YOUR_TOKEN',
'Accept: application/json'
]);
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 自簽憑證 | 開發/測試環境使用自我簽署憑證 | 將自簽 CA 加入系統信任存儲,或 CURLOPT_CAINFO 指定 |
| 憑證鏈不完整 | 伺服器未正確配置中繼憑證 | 聯繫 API 提供方修正,或暫時關閉驗證 (僅測試) |
| 主機名不符 | CN/SAN 不包含請求的域名 | 檢查 DNS/Hosts,或 CURLOPT_SSL_VERIFYHOST=0 (危險) |
| 憑證過期 | 伺服器憑證已過期 | 聯繫提供方更新,切勿長期忽略 |
| 系統 CA 過舊 | 作業系統 ca-certificates 套件版本過舊 | 更新套件或手動更新 cacert.pem |