
先說一個結論代碼里蹦出[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed這一長串報錯大多數(shù)情況下不是你的業(yè)務代碼邏輯出了問題而是當前運行環(huán)境里缺少一份能被信任的根證書清單。你向某個 HTTPS 地址發(fā)請求服務器把自己的證書遞過來本地的證書校驗組件核對一番之后直接表示不信任TLS 握手就會停在最后一步于是異常信息里除了證書驗證失敗之外還會帶上“握手失敗”的描述。這個問題我在幾個項目里都踩過。某次內(nèi)部報表服務調(diào)用另一個團隊的結算接口請求發(fā)過去就拋這個錯當時排查了大半天最后發(fā)現(xiàn)只是服務器上的根證書庫沒更新。還有一次是 Python 爬蟲在灰度環(huán)境里批量抓數(shù)據(jù)跑著跑著突然成片報錯原因是調(diào)試代理工具替換了證書代碼里又沒把對應 CA 加入信任列表。不同語言、不同框架下的報錯原文不完全一樣但它們背后的機制是相通的要么證書鏈斷了要么根證書缺失要么中間層的證書被換了。這篇文章把 root cause、排查方法、各語言下的修復方式都捋一遍碰到同類問題可以直接照著處理。1. Cert verify failed 是什么先搞清楚 TLS 握手里發(fā)生了什么1.1 報錯長什么樣通常發(fā)生在哪些場景先看幾個典型的報錯形態(tài)。Python 的 requests 或者 urllib3 通常會拋requests.exceptions.SSLError: HTTPSConnectionPool(hostapi.example.com, port443): Max retries exceeded with url: /v1/data (Caused by SSLError(SSLCertVerificationError(1, [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1125))))Node.js 里請求 HTTPS 接口時可能會看到Error: certificate has expired at TLSSocket.onConnectSecure ...用 curl 命令行訪問時也可能直接輸出curl: (60) SSL certificate problem: unable to get local issuer certificate雖然各家報錯措辭不同但關鍵點都指向同一件事無法獲得本地簽發(fā)者證書也就是無法完成證書鏈的校驗。這說明客戶端在驗證“對方證書是不是由可信的 CA 簽發(fā)”時找不到可信的根。這種問題經(jīng)常出現(xiàn)在這么幾個場景里新裝好的 Python、Node.js 環(huán)境直接跑 HTTPS 請求就報錯系統(tǒng)證書庫本身不完整。內(nèi)網(wǎng)服務調(diào)用對方使用了私有 CA 簽發(fā)的證書本機沒有信任這個 CA。本地聯(lián)調(diào)時開了抓包代理代理生成的根證書沒導入系統(tǒng)信任區(qū)。服務器系統(tǒng)時間不對導致證書有效性判斷失敗。某個服務長期運行系統(tǒng)根證書過期后沒有執(zhí)行系統(tǒng)更新。1.2 TLS 握手的基本流程與證書驗證的位置要弄明白這個錯需要回顧一下 TLS 握手過程。以 TLS 1.2 為例一次正常的握手大致經(jīng)過這么幾步ClientHello客戶端告訴服務器支持的 TLS 版本、加密套件以及一個隨機數(shù)。ServerHello服務器選擇協(xié)議版本和加密套件也發(fā)一個隨機數(shù)。服務器發(fā)送 Certificate把自己的證書一般是證書鏈包括葉子證書和中間證書交給客戶端。服務器發(fā)送 ServerKeyExchange / CertificateRequest按需和 ServerHelloDone。客戶端驗證服務器證書生成預主密鑰pre-master secret用服務器公鑰加密后送回。雙方各自計算會話密鑰交換 ChangeCipherSpec完成握手。CERTIFICATE_VERIFY_FAILED發(fā)生在第 5 步也就是客戶端驗證服務器證書的階段。客戶端拿到服務器傳來的證書之后要沿著證書鏈一層一層往上找信任錨最終必須落到一個本機預先信任的根證書上。任何一環(huán)斷裂或者某個證書過期、被撤銷、主機名不匹配驗證都會失敗。TLS 1.3 在握手階段把所有證書消息合并在一起順序稍有不同但驗證邏輯一致。所謂的“握手失敗”本質(zhì)就是驗證失敗之后客戶端拒絕繼續(xù)協(xié)商密鑰直接中斷連接。所以這個錯誤看起來是“連接層”報錯實際是“信任層”問題。1.3 證書驗證失敗的幾個信任原則理解這個錯誤還要記住證書驗證的三個基本原則鏈式信任終端實體證書必須由可信 CA 簽發(fā)。證書鏈可以一層套一層但最終要能找到本機信任庫里的根證書。有效期校驗每個證書都有生效時間和失效時間。系統(tǒng)時間超出這個范圍驗證就會失敗。尤其要注意服務器證書本身有效但中間證書或根證書過期同樣會導致失敗。用途校驗證書的擴展項里限制了用途比如只能用于服務端認證。如果請求的連接方式與證書用途不匹配也會報錯。很多人在排查時報錯定位到了“證書驗證失敗”就停了其實應該繼續(xù)追問是哪一層驗證失敗是找不到根證書還是證書鏈不完整還是時間不正確還是主機名不匹配這幾個方向?qū)耆煌男迯褪侄巍?. 六大根因定位為什么證書驗證會失敗2.1 根證書缺失或過期是最常見的原因開發(fā)機、測試服務器上的根證書不是一成不變的。操作系統(tǒng)和運行語言各自維護一份證書庫。常見的 Linux 發(fā)行版安裝了ca-certificates包之后根證書會放在/etc/ssl/certs/下并生成一個哈希索引。如果這個包長期沒更新新出現(xiàn)的商業(yè) CA 根證書就不會出現(xiàn)在里邊客戶端自然無法驗證新簽發(fā)的服務器證書。某些國外或國內(nèi) CA 在特定時間段啟用了新的根證書同時服務器端只發(fā)新鏈不完全兼容舊根。這種情況在老舊鏡像里特別容易出現(xiàn)因為鏡像構建時打包的系統(tǒng)包快照已經(jīng)過時。現(xiàn)象就是同樣的代碼在本機能跑通放到某個服務器或容器的鏡像里就報錯這就是兩個環(huán)境的證書庫內(nèi)容不一致。2.2 語言運行環(huán)境與系統(tǒng)證書庫不同步Python 是一個重災區(qū)。Python 默認使用 OpenSSL 來執(zhí)行 TLS 連接而 OpenSSL 讀取證書庫的位置取決于編譯參數(shù)和運行環(huán)境配置。有些 Python 發(fā)行版在安裝時會附帶自己的 OpenSSL 庫和證書文件它可能并不讀取系統(tǒng)的/etc/ssl/certs而是讀取安裝目錄下的某個證書文件。這就解釋了為什么會出現(xiàn)“明明系統(tǒng)配置好了 CAPython 還是報錯”的怪現(xiàn)象。你在系統(tǒng)層面信任的根證書跟 Python 進程實際讀取的根證書可能是兩份完全不同的文件。Node.js 也一樣。Node 的 TLS 實現(xiàn)默認使用系統(tǒng)內(nèi)置的根證書集合但當運行環(huán)境中存在NODE_EXTRA_CA_CERTS變量時加載邏輯又會發(fā)生變化。如果該文件路徑不存在或格式不合法Node 可能退回到不完整的內(nèi)置證書集合甚至導致額外的驗證失敗。2.3 系統(tǒng)時間漂移導致的驗證失敗證書有效期校驗依賴一方是相對精確的系統(tǒng)時間。如果服務器或客戶端的時間差了很遠比如分鐘級錯誤證書的“not before”和“not after”判斷就會失敗。曾有一個案例一臺物理機因為主板 CMOS 電池沒電系統(tǒng)時間停在了初始化那一年跑任何 HTTPS 請求全部報證書驗證失敗。用date命令看了一眼時間立刻反應過來。還有一種隱蔽情況是虛擬機或容器的時區(qū)設置正確但硬件時鐘偏低。Linux 虛機沒有同步硬件時間啟動后時間停留在上次關機時間點也會觸發(fā)這個問題。2.4 本地開啟調(diào)試代理或抓包工具導致證書被替換客戶端與服務器之間如果多了一層代理代理為了解密流量會生成自己的 CA 證書并用這個 CA 動態(tài)簽發(fā)目標域名的證書從而實現(xiàn)中間人解密。Charles、Fiddler、Wireshark 配合的相關 SSL 代理還有瀏覽器抓包插件都是這種做法。當你開著這種代理代碼里的 HTTPS 請求實際拿到的并不是服務器真正的證書而是代理簽發(fā)的證書。只要代理的根證書沒有被加入系統(tǒng)或語言的信任區(qū)證書驗證一定失敗。此時你去看報錯的服務器證書信息會發(fā)現(xiàn)它指向的域名雖然是目標域名但證書鏈頂端不是公開的根 CA而是代理自己的根證書。這種情況在本地開發(fā)時非常常見不少人折騰半天甚至還會反過來懷疑是服務器證書出了問題。2.5 自簽名證書與私有 CA 的信任問題有些內(nèi)網(wǎng)系統(tǒng)、開發(fā)環(huán)境、或者私有化交付的軟件會使用自簽名證書。自簽名證書沒有上級 CA 為其背書它的信任和權威完全取決于客戶端是否提前導入。如果代碼所在機器沒有導入對應的證書驗證必然失敗。私有 CA 情況稍微復雜一點。它雖然有自己的根證書和中間證書但公共系統(tǒng)默認不會信任這些私有根。你需要把私有根證書導入操作系統(tǒng)、語言、或者在代碼里顯式指定。2.6 服務器端證書鏈不完整客戶端驗證證書鏈時需要拿到完整鏈路。如果服務器只發(fā)了葉子證書而沒有把中間證書一并發(fā)送客戶端在本地證書庫中找不到對應的上級證書就會提示unable to get local issuer certificate。這種情況適合用openssl s_client直接檢查服務器返回的證書鏈能一眼看出少了一層。3. 實操一套通用的修復流程3.1 先確認到底是誰的證書不信任拿到這個報錯之后第一步不是改代碼而是驗證服務端證書本身??梢韵扔妹钚泄ぞ呖匆谎蹖Ψ椒掌鞣祷氐淖C書鏈openssl s_client -connect api.example.com:443 -showcerts -servername api.example.com如果verify return code: 0 (ok)說明服務端證書鏈本身沒問題問題大概率出在本機信任庫。如果返回不是 0比如unable to get local issuer certificate或unable to verify the first certificate要重點檢查服務器發(fā)送的證書鏈是否完整。再檢查本機時間date偏差超過幾分鐘可以直接修正時間后再測。這是成本最低的排查手段。檢查當前環(huán)境的證書庫是否完整也是這里的關鍵步驟。3.2 更新系統(tǒng)級證書庫如果是系統(tǒng)證書庫過期在 Debian 系的 Linux 發(fā)行版上可以執(zhí)行sudo apt-get update sudo apt-get install --reinstall ca-certificates sudo update-ca-certificatesupdate-ca-certificates命令會掃描/usr/local/share/ca-certificates/下的自定義證書并重新生成/etc/ssl/certs/下的證書索引。執(zhí)行完之后大部分的“系統(tǒng)級”報錯都能解決。RHEL 系的發(fā)行版使用update-ca-trustsudo yum install -y ca-certificates sudo update-ca-trust force-enable sudo update-ca-trust extract更新完系統(tǒng)證書庫之后用 curl 再測試一次同一個地址curl https://api.example.com/v1/data如果 curl 已經(jīng)能連通說明問題確實出在系統(tǒng)信任層。后面再逐個檢查語言環(huán)境。3.3 Python 環(huán)境里利用 certifi 顯式指定證書在 Python 里最簡單可靠的修復就是把 requests 使用的證書指向 certifi 提供的 CA 包。certifi 是一個維護得很及時的根證書集合它會跟隨 requests 等庫一起分發(fā)。pip install --upgrade certifi然后在代碼里顯式傳入import requests import certifi resp requests.get(https://api.example.com/v1/data, verifycertifi.where())這樣 requests 在構建 SSLContext 時會使用 certifi 的 CA 文件而不是默認的 system store。如果項目里用到了 urllib3 的 PoolManager也可以指定 ca_certsimport urllib3 import certifi http urllib3.PoolManager( cert_reqsCERT_REQUIRED, ca_certscertifi.where() ) resp http.request(GET, https://api.example.com/v1/data)如果系統(tǒng)級 CA 已經(jīng)修好理論上 requests 也會正常工作。但顯式指定 certifi 有個好處它不依賴系統(tǒng)目錄權限也不怕 Python 環(huán)境里的 OpenSSL 配置混亂。我建議在腳本化的項目里直接把它寫進公共請求封裝里。對于 aiohttp 或 httpx則可以分別參考下面的寫法import aiohttp import certifi import ssl ssl_ctx ssl.create_default_context(cafilecertifi.where()) async with aiohttp.ClientSession(connectoraiohttp.TCPConnector(sslssl_ctx)) as session: ...import httpx client httpx.Client(verifycertifi.where())3.4 設置環(huán)境變量解決 OpenSSL 證書路徑問題OpenSSL 類的程序會讀取SSL_CERT_FILE和SSL_CERT_DIR環(huán)境變量。當發(fā)現(xiàn) OpenSSL 讀的證書跟系統(tǒng)不同步時可以直接把變量指到系統(tǒng)證書目錄export SSL_CERT_FILE/etc/ssl/certs/ca-certificates.crt export SSL_CERT_DIR/etc/ssl/certs也可以指到 certifi 的證書文件export SSL_CERT_FILE$(python -m certifi)這種方式對很多用 libcurl 編寫、或者使用系統(tǒng) OpenSSL 的組件有效。把它寫進項目的啟動腳本或者部署配置里不用改代碼就能全局生效。CURL_CA_BUNDLE環(huán)境變量可以影響 curl 的證書加載不過沒有SSL_CERT_FILE的范圍廣兩處最好保持一致。3.5 Node.js 導入額外 CANode.js 項目如果遇到系統(tǒng) CA 不匹配臨時方式是指定NODE_EXTRA_CA_CERTS環(huán)境變量指向一個包含額外 CA 證書的文件export NODE_EXTRA_CA_CERTS/path/to/extra-ca.pem node app.js這個環(huán)境變量會把指定的 CA 追加到 Node 默認證書集合里。它適合處理私有 CA、企業(yè)內(nèi)網(wǎng) CA 的場景。如果是全局信任的問題可以把根證書放到/etc/ssl/certs/并執(zhí)行update-ca-certificatesNode 在多數(shù) Linux 發(fā)行版上會讀取系統(tǒng)信任鏈。不過要注意不同版本的 Node 對系統(tǒng)證書庫的加載方式略有差異某些版本基于內(nèi)置的根證書列表某些版本會直接走系統(tǒng) OpenSSL。所以最穩(wěn)妥的方式是環(huán)境變量顯式追加。npm 如果連 registry 都拉不動出現(xiàn)證書驗證失敗可以優(yōu)先看下 npm 的配置npm config list檢查cafile和strict-ssl配置。正常情況下strict-ssl應該保持true不要為了省事把它關掉。如果內(nèi)網(wǎng) npm 源使用私有證書用cafile指向?qū)C書文件即可。4. 自簽名證書與私有 CA 的工程化處理4.1 導出證書內(nèi)容與抓取證書鏈處理自簽名證書最常用的命令是通過openssl s_client拿到證書鏈并導出openssl s_client -connect inner.example.com:443 -showcerts -servername inner.example.com /dev/null /tmp/chain.pem文件里會有多個證書塊。找到根證書一般是最后一個證書塊保存為my-ca.crt。如果連的是內(nèi)網(wǎng) HTTPS 服務且先前能通過瀏覽器訪問也可以在瀏覽器里點擊地址欄的鎖圖標選擇“查看證書”導出根證書。導出格式選 Base64 編碼的 PEM 格式就可以。4.2 信任自簽名證書或私有 CA拿到根證書之后在系統(tǒng)層面配置信任。Debian 系發(fā)行版的做法是把證書放到/usr/local/share/ca-certificates/目錄下文件后綴必須是.crtsudo cp my-ca.crt /usr/local/share/ca-certificates/my-ca.crt sudo chmod 644 /usr/local/share/ca-certificates/my-ca.crt sudo update-ca-certificates這樣系統(tǒng)級信任就建立起來了curl、一部分通過系統(tǒng) OpenSSL 入網(wǎng)的程序都會生效。如果不想動系統(tǒng)目錄只影響單個 Python 項目就把根證書文件放到項目里并在 requests 請求時指定requests.get(https://inner.example.com, verify./certs/my-ca.crt)Node.js 側繼續(xù)用NODE_EXTRA_CA_CERTS環(huán)境變量處理。4.3 容器環(huán)境里的證書更新Docker 鏡像是證書驗證失敗的另一個常見場景。鏡像往往是在幾個月甚至一年前構建的內(nèi)部沒有運行包管理器的升級流程。運行鏡像后連接互聯(lián)網(wǎng) HTTPS 服務報錯提示找不到簽發(fā)者證書。推薦的做法是修改 Dockerfile在構建階段刷新證書庫RUN apt-get update \ apt-get install -y ca-certificates \ rm -rf /var/lib/apt/lists/*如果鏡像運行時需要訪問內(nèi)網(wǎng)服務且內(nèi)網(wǎng)服務使用私有 CA可以把 CA 證書 COPY 進鏡像然后在構建時執(zhí)行COPY my-ca.crt /usr/local/share/ca-certificates/my-ca.crt RUN chmod 644 /usr/local/share/ca-certificates/my-ca.crt update-ca-certificates在 Kubernetes 或容器平臺里也可以把 CA 以 ConfigMap 掛載到指定位置然后讓應用啟動腳本在執(zhí)行時調(diào)用update-ca-certificates。不過掛載目錄的動態(tài)更新不會自動刷新系統(tǒng)證書索引所以更可靠的方案是在啟動命令里加一步調(diào)用。4.4 代碼里臨時信任一張證書的注意點調(diào)試階段如果你只是臨時請求某個內(nèi)網(wǎng)端口測試可以加載證書對象import ssl import socket hostname inner.example.com port 443 context ssl.create_default_context(cafile./my-ca.crt) with socket.create_connection((hostname, port)) as sock: with context.wrap_socket(sock, server_hostnamehostname) as tls_sock: print(tls_sock.version())這個方式?jīng)]有全局修改任何系統(tǒng)狀態(tài)適合快速驗證一個證書是否有效。但我得特別提醒盡量不要在真實代碼中使用verifyFalse來掩蓋錯誤。那是把安全防線直接拆掉。這個寫法只能用于徹底隔離的調(diào)試環(huán)境而且換環(huán)境前必須改回。真到了生產(chǎn)環(huán)境無論如何都要維護正確的 CA 鏈。4.5 自己簽發(fā)證書的正確姿勢如果你每天都在自簽證書建議不要直接簽出葉子證書而是先創(chuàng)建一套私有 CA然后由這套 CA 簽發(fā)各個服務的證書。這樣只需要在客戶端信任這一個 CA 根證書以后簽新證書不用再逐臺機器導入。創(chuàng)建私有 CA 的最小流程是# 生成 CA 私鑰 openssl genrsa -out ca-key.pem 2048 # 生成 CA 自簽名證書 openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \ -subj /CNInternal Demo CA之后每個服務生成自己的私鑰和證書簽署請求openssl req -newkey rsa:2048 -nodes -keyout service-key.pem \ -out service.csr -subj /CNinner.example.com openssl x509 -req -days 825 -in service.csr \ -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial \ -out service-cert.pem -extfile ext.confext.conf里配置subjectAltNameDNS:inner.example.com這一步不能省?,F(xiàn)代客戶端校驗證書時基本都會檢查 SAN忽略它的話即使 CA 已信任也無法避免主機名不匹配的問題。5. 不同場景下的修復差異與常見問題速查5.1 報錯信息對照與處理方法報錯關鍵字可能原因優(yōu)先處理方式unable to get local issuer certificate證書鏈不完整或根證書缺失更新系統(tǒng)證書庫或顯式指定 CA 文件certificate has expired證書過期或系統(tǒng)時間偏差檢查服務端證書同時校對本機時間self-signed certificate對方使用自簽證書導入自簽根證書或私有 CAhostname mismatch域名與證書 CN/SAN 不匹配使用正確的服務域名訪問或重新簽發(fā)匹配域名的證書wrong version number該地址其實不是 HTTPS 或端口不對檢查協(xié)議與端口配置tlsv1 alert protocol version本地 TLS 版本過低或服務器不支持該版本升級 OpenSSL 與運行語言版本5.2 Python 的 Mac 開發(fā)環(huán)境特殊案例在 macOS 上裝完 Python有時會出現(xiàn)“明明瀏覽器訪問都沒問題但 Python 請求就報證書驗證失敗”。這通常是因為 Python 在安裝時沒有自動讀取系統(tǒng)鑰匙串里的根證書。處理辦法是安裝 certifi 并配置默認證書pip install certifi然后可以直接把 certifi 的證書路徑設置為 Python 環(huán)境變量export SSL_CERT_FILE$(python3 -m certifi)把這一行寫進~/.zshrc或~/.bashrc。經(jīng)過這個調(diào)整Python 的 URLLIB 和 requests 就能正常訪問大多數(shù) HTTPS 服務了。如果代理工具也在用確認代理根證書同時加入了鑰匙串與 certifi 文件。5.3 Node.js 請求外部服務時報 verify 錯誤Node.js 的fetch或http模塊走的是內(nèi)置 TLS。遇到證書錯誤時先檢查是不是有NODE_EXTRA_CA_CERTS這個變量被設置成了不存在的文件路徑。有時候全局配置里殘留了早期調(diào)試的 export導致 Node 加載異常。用下面的命令確認node -p process.env.NODE_EXTRA_CA_CERTS如果為空或已失效直接重設后再啟動應用。還想再提供一個小命令查看 Node 實際使用的 CA 數(shù)量node -e console.log(require(crypto).X509Certificate.prototype.toString.length)這個不太常用大多數(shù)時候直接看環(huán)境變量和系統(tǒng)證書庫就夠了。5.4 調(diào)試代理場景下的證書修復本地開著抓包代理時出現(xiàn)這個報錯我的處理順序是先導出代理根證書。把這個根證書導入到系統(tǒng)信任區(qū)。Windows 證書管理器、macOS 鑰匙串、Linux 的/usr/local/share/ca-certificates/都可以。對 Python如果 requests 顯式指定了verifycertifi.where()還需要把代理根證書追加到 certifi 的證書文件里。對 Node.js可以通過NODE_EXTRA_CA_CERTS指定包含代理根證書的文件。驗證時間與代理自身的有效性。debug 完成關閉代理以后如果不再需要把信任的根證書移除避免以后對正式環(huán)境的請求誤信任代理 CA。5.5 檢查證書庫是否生效更新完證書之后要驗證是否真正生效。Python 里可以這樣查看當前信任的證書路徑import ssl print(ssl.get_default_verify_paths())輸出里cafile和capath就是當前默認配置會去尋找的證書位置。如果和預期不符就知道問題出在哪一層。還可以用 Python 直接確認某個證書文件能否被 OpenSSL 正確讀取python -c import ssl; ssl.create_default_context(cafile/etc/ssl/certs/ca-certificates.crt); print(ok)如果打印 ok說明證書文件格式和路徑都沒問題。5.6 生產(chǎn)環(huán)境里要不要關驗證聽我一句生產(chǎn)環(huán)境里不要通過關閉驗證來修復問題。verifyFalse是一時爽后續(xù)是災難。一旦 TLS 校驗失效中間人攻擊就變成了實際風險而且出現(xiàn)問題后很難排查。生產(chǎn)環(huán)境正確的做法是把 CA 納入版本管理隨代碼一起部署并在部署腳本里執(zhí)行證書刷新。把 CA 集中在一個受控倉庫里每次有證書更新就走變更流程才是一套可持續(xù)的方案。6. 我踩過的一些坑和最終建議最后分享幾條在實際處理中總結出的經(jīng)驗。第一碰到這個報錯先修系統(tǒng)時間再做后續(xù)排查。時間的坑太隱蔽了往往會消耗很久。我以前有一次排查接口報錯檢查了證書鏈、環(huán)境變量、代理配置最后才發(fā)現(xiàn)是測試服務器的時間與標準時間差了將近三小時。當時的感覺真是哭笑不得。第二Python 跟系統(tǒng)證書不要混在一起想。系統(tǒng) CA 更新了不代表 Python 就能立刻讀到。很多人在服務器上執(zhí)行完update-ca-certificates以為 Python 會自動好起來實際上 Python 的 OpenSSL 不一定讀取這個路徑。顯式傳verifycertifi.where()或者設置SSL_CERT_FILE是最穩(wěn)的。第三調(diào)試代理留下的根證書要記得清理。曾經(jīng)一個項目在代碼里加入了抓包工具的根證書用于本地調(diào)試結果部署到預發(fā)布環(huán)境后忘了解除預發(fā)布環(huán)境的所有 HTTPS 請求都經(jīng)過了代理 CA 信任鏈幸好監(jiān)控發(fā)現(xiàn)得早否則生產(chǎn)環(huán)境也會帶病上線。如果你有類似調(diào)試工具一定要按環(huán)境去隔離信任配置不要一份配置通吃全部環(huán)境。第四容器鏡像的證書更新要寫進構建流程。不要依賴某個基礎鏡像自帶的證書因為鏡像構建日期早、證書過期是很容易被忽略的定時炸彈。加一行apt-get install ca-certificates并清理緩存成本極低收益卻很直接。這幾招用下來絕大多數(shù)CERTIFICATE_VERIFY_FAILED問題都能在幾分鐘內(nèi)定位。以后再看到“握手失敗”就不至于手足無措了。