前言
在維護 Laradock 或其他 Docker PHP 環境時,經常會遇到舊版容器因 CA 憑證過期,導致無法正常執行 HTTPS 請求的問題。本文將詳細說明問題的診斷方式與解決方案,適用於 php-fpm 和 php-worker 容器。
問題現象
錯誤訊息一:curl 錯誤
curl: (60) SSL certificate problem: unable to get local issuer 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.
錯誤訊息二:PHP 錯誤
PHP Warning: file_get_contents(): SSL operation failed with code 1.
OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed
PHP Warning: file_get_contents(): Failed to enable crypto
PHP Warning: file_get_contents(https://example.com): failed to open stream: operation failed
錯誤訊息三:Composer 錯誤
The "https://repo.packagist.org/packages.json" file could not be downloaded:
SSL operation failed with code 1.
錯誤訊息四:Laravel/Guzzle HTTP 錯誤
cURL error 60: SSL certificate problem: unable to get local issuer certificate
問題原因分析
根本原因
Docker image 內建的 CA(Certificate Authority)根憑證過期或版本過舊,無法驗證新簽發的 SSL 憑證鏈。
常見情境
- 使用舊版 base image:例如
alpine:3.12、php:7.4-alpine3.12等 2020-2021 年的 image - Let’s Encrypt 憑證鏈變更:2021 年 9 月 Let’s Encrypt 的 DST Root CA X3 過期,影響許多舊系統
- 長期未更新的容器:容器內的 ca-certificates 套件長期未更新
- 自建 image 未包含憑證更新:Dockerfile 中未加入憑證更新步驟
影響範圍
- curl 命令
- PHP 的
file_get_contents()、fopen()等函數 - PHP cURL 擴展
- Composer 套件管理
- Guzzle HTTP Client
- Laravel HTTP Client
- 任何需要 HTTPS 連線的 PHP 應用
診斷步驟
步驟一:進入容器
# 進入 php-fpm 容器
docker-compose exec php-fpm sh
# 或進入 php-worker 容器
docker-compose exec php-worker sh
# 如果容器已停止,使用 run 指令
docker-compose run --rm php-fpm sh
步驟二:測試 curl HTTPS 連線
curl -v https://www.google.com/
觀察輸出,重點關注:
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
* CApath: none
* TLSv1.2 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
這表示 curl 使用 /etc/ssl/certs/ca-certificates.crt 作為 CA 憑證檔案,但驗證失敗。
步驟三:檢查 PHP OpenSSL 憑證路徑
php -r "var_dump(openssl_get_cert_locations());"
輸出範例:
array(8) {
["default_cert_file"]=>
string(17) "/etc/ssl/cert.pem"
["default_cert_file_env"]=>
string(13) "SSL_CERT_FILE"
["default_cert_dir"]=>
string(14) "/etc/ssl/certs"
["default_cert_dir_env"]=>
string(12) "SSL_CERT_DIR"
["default_private_dir"]=>
string(16) "/etc/ssl/private"
["default_default_cert_area"]=>
string(8) "/etc/ssl"
["ini_cafile"]=>
string(0) ""
["ini_capath"]=>
string(0) ""
}
重要欄位說明:
| 欄位 | 說明 |
|---|---|
default_cert_file | PHP OpenSSL 預設讀取的憑證檔案路徑 |
default_cert_dir | PHP OpenSSL 預設讀取的憑證目錄 |
ini_cafile | php.ini 中設定的 openssl.cafile |
ini_capath | php.ini 中設定的 openssl.capath |
步驟四:檢查憑證檔案狀態
# 檢查 /etc/ssl/ 目錄結構
ls -la /etc/ssl/
# 檢查憑證檔案詳細資訊
ls -la /etc/ssl/certs/ca-certificates.crt
ls -la /etc/ssl/cert.pem
# 如果 PHP 使用 /usr/lib/ssl/
ls -la /usr/lib/ssl/cert.pem 2>/dev/null || echo "路徑不存在"
輸出範例(問題狀態):
-rw-r--r-- 1 root root 214057 Apr 15 2021 ca-certificates.crt
lrwxrwxrwx 1 root root 25 Apr 14 2021 cert.pem -> certs/ca-certificates.crt
判斷標準:如果憑證檔案日期是 2021 年或更早,很可能已經過期或缺少新的 CA 憑證。
步驟五:測試 PHP HTTPS 連線
# 測試 file_get_contents
php -r "echo @file_get_contents('https://www.google.com/') ? 'OK' : 'FAIL';"
# 測試 curl 擴展
php -r "
\$ch = curl_init('https://www.google.com/');
curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
\$result = curl_exec(\$ch);
if (curl_errno(\$ch)) {
echo 'Error: ' . curl_error(\$ch);
} else {
echo 'OK';
}
curl_close(\$ch);
"
解決方案
方案一:容器內手動修復(臨時方案)
適用於快速修復已運行的容器,但容器重建後會失效。
# 1. 下載最新 CA 憑證(使用 -k 跳過 SSL 驗證)
curl -k -o /tmp/cacert.pem https://curl.se/ca/cacert.pem
# 2. 備份原有憑證(可選)
cp /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt.bak
# 3. 更新憑證檔案
cp /tmp/cacert.pem /etc/ssl/certs/ca-certificates.crt
# 4. 根據 PHP 實際讀取路徑更新(檢查 openssl_get_cert_locations 的結果)
# 如果 default_cert_file 是 /etc/ssl/cert.pem
cp /tmp/cacert.pem /etc/ssl/cert.pem
# 如果 default_cert_file 是 /usr/lib/ssl/cert.pem
mkdir -p /usr/lib/ssl
cp /tmp/cacert.pem /usr/lib/ssl/cert.pem
# 5. 清理暫存檔
rm /tmp/cacert.pem
# 6. 驗證修復結果
curl -v https://www.google.com/
php -r "echo @file_get_contents('https://www.google.com/') ? 'PHP OK' : 'PHP FAIL';"
方案二:修改 Dockerfile(永久方案)
Alpine Linux base image(php-fpm / php-worker)
#--------------------------------------------------------------------------
# 更新 CA 憑證
#--------------------------------------------------------------------------
# 解決舊版 Alpine image CA 憑證過期問題
# 必須在其他需要 HTTPS 的操作之前執行
RUN apk add --no-cache ca-certificates curl \
&& update-ca-certificates \
&& curl -k -o /tmp/cacert.pem https://curl.se/ca/cacert.pem \
&& cp /tmp/cacert.pem /etc/ssl/certs/ca-certificates.crt \
&& cp /tmp/cacert.pem /etc/ssl/cert.pem \
&& rm /tmp/cacert.pem \
&& echo "CA certificates updated successfully"
Debian/Ubuntu base image
#--------------------------------------------------------------------------
# 更新 CA 憑證
#--------------------------------------------------------------------------
RUN apt-get update \
&& apt-get install -yqq --no-install-recommends ca-certificates curl \
&& update-ca-certificates \
&& curl -k -o /tmp/cacert.pem https://curl.se/ca/cacert.pem \
&& cp /tmp/cacert.pem /etc/ssl/certs/ca-certificates.crt \
&& ln -sf /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem \
&& rm /tmp/cacert.pem \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& echo "CA certificates updated successfully
Laradock 完整範例
php-fpm/Dockerfile 修改
ARG LARADOCK_PHP_VERSION
FROM php:${LARADOCK_PHP_VERSION}-fpm
LABEL maintainer="Your Name <your@email.com>"
ARG LARADOCK_PHP_VERSION
#--------------------------------------------------------------------------
# 更新 CA 憑證(必須在最前面)
#--------------------------------------------------------------------------
RUN apt-get update \
&& apt-get install -yqq --no-install-recommends ca-certificates curl \
&& update-ca-certificates \
&& curl -k -o /tmp/cacert.pem https://curl.se/ca/cacert.pem \
&& cp /tmp/cacert.pem /etc/ssl/certs/ca-certificates.crt \
&& ln -sf /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem \
&& rm /tmp/cacert.pem \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
#--------------------------------------------------------------------------
# 其他套件安裝...
#--------------------------------------------------------------------------
# 以下為原有的 Dockerfile 內容
php-worker/Dockerfile 修改(Alpine)
ARG LARADOCK_PHP_VERSION
FROM php:${LARADOCK_PHP_VERSION}-alpine3.12
LABEL maintainer="Your Name <your@email.com>"
ARG LARADOCK_PHP_VERSION
#--------------------------------------------------------------------------
# 更新 CA 憑證(必須在最前面)
#--------------------------------------------------------------------------
RUN apk --update add \
ca-certificates \
curl \
wget \
&& update-ca-certificates \
&& curl -k -o /tmp/cacert.pem https://curl.se/ca/cacert.pem \
&& cp /tmp/cacert.pem /etc/ssl/certs/ca-certificates.crt \
&& cp /tmp/cacert.pem /etc/ssl/cert.pem \
&& rm /tmp/cacert.pem
#--------------------------------------------------------------------------
# 安裝其他套件
#--------------------------------------------------------------------------
RUN apk --update add \
git \
build-base \
libmcrypt-dev \
libxml2-dev \
pcre-dev \
zlib-dev \
autoconf \
cyrus-sasl-dev \
libgsasl-dev \
oniguruma-dev \
libressl \
libressl-dev \
supervisor
# ... 其餘 Dockerfile 內容
重建容器
# 重建 php-fpm
docker-compose build --no-cache php-fpm
# 重建 php-worker
docker-compose build --no-cache php-worker
# 重新啟動服務
docker-compose up -d php-fpm php-worker
# 驗證修復
docker-compose exec php-fpm php -r "echo @file_get_contents('https://www.google.com/') ? 'OK' : 'FAIL';"
docker-compose exec php-worker php -r "echo @file_get_contents('https://www.google.com/') ? 'OK' : 'FAIL';"
替代方案:php.ini 設定
如果不想修改 Dockerfile,可以透過 php.ini 指定憑證路徑:
[openssl]
openssl.cafile=/path/to/cacert.pem
openssl.capath=/etc/ssl/certs
或在 PHP 程式碼中動態設定:
// 設定 stream context
$context = stream_context_create([
'ssl' => [
'cafile' => '/path/to/cacert.pem',
'verify_peer' => true,
'verify_peer_name' => true,
]
]);
$content = file_get_contents('https://example.com/', false, $context);
// 設定 curl
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.com/');
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
注意:不建議設定 verify_peer = false,這會完全關閉 SSL 驗證,造成安全風險。
常見憑證路徑對照表
| 系統/環境 | curl 憑證路徑 | PHP OpenSSL 憑證路徑 |
|---|---|---|
| Alpine Linux | /etc/ssl/certs/ca-certificates.crt | /etc/ssl/cert.pem |
| Debian/Ubuntu | /etc/ssl/certs/ca-certificates.crt | /etc/ssl/certs/ca-certificates.crt |
| CentOS/RHEL | /etc/pki/tls/certs/ca-bundle.crt | /etc/pki/tls/cert.pem |
| 部分 PHP 編譯版本 | – | /usr/lib/ssl/cert.pem |
疑難排解
Q1: 更新憑證後仍然失敗?
檢查是否所有路徑都已更新:
# 找出所有可能的憑證路徑
find /etc/ssl -name "*.pem" -o -name "*.crt" 2>/dev/null
find /usr/lib/ssl -name "*.pem" 2>/dev/null
find /usr/share/ca-certificates -type f 2>/dev/null
Q2: 如何確認憑證檔案是否有效?
# 檢查憑證檔案內容
openssl x509 -in /etc/ssl/certs/ca-certificates.crt -text -noout | head -20
# 測試特定網站的憑證鏈
openssl s_client -connect www.google.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt
Q3: Composer 仍然報錯?
清除 Composer 快取後重試:
composer clear-cache
composer install
Q4: 如何自動化憑證更新?
在 CI/CD 流程中加入定期重建 image 的步驟,或使用以下 cron 任務:
# 每月更新一次憑證
0 0 1 * * curl -o /etc/ssl/certs/ca-certificates.crt https://curl.se/ca/cacert.pem
參考資源
總結
解決 Docker PHP 容器 SSL 憑證問題的關鍵步驟:
- 診斷:使用
curl -v和openssl_get_cert_locations()確認問題和路徑 - 下載:從 curl.se 取得最新 CA bundle
- 更新:將憑證複製到 curl 和 PHP 實際讀取的路徑
- 驗證:測試 HTTPS 連線是否正常
- 永久化:修改 Dockerfile 並重建 image
最後更新:2026年 1 月
適用環境:Laradock php-fpm、php-worker、Alpine Linux、Debian/Ubuntu