如何解决 InsecureRequestWarning: Unverified HTTPS request is being made to host '47.113.219.226'. Adding certificate verification is strongly advised.
问题(全网解决方案大全)
在使用 Python 的 requests
库(或其底层依赖 urllib3
)发起 HTTPS 请求时,若没有对目标主机的 TLS 证书进行校验,就会出现类似如下的警告(Warning):
InsecureRequestWarning: Unverified HTTPS request is being made to host '47.113.219.226'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
它的含义是:你正在向 https://47.113.219.226
发起一个不经过证书验证的 HTTPS 请求。如果对方使用的是自签名证书、或者根本不存在可信的 TLS 证书,就会触发这一警告。简单来说,这是在提醒你:
这篇技术博客将会“超详细”地汇集来自全网的各种解决方案,从“最急就章”的临时抑制到“根本治本”的正规证书校验,力求读者在开发、测试、生产不同场景下都能找到最合适、最全面的处理方法。
大家好,我是 猫头虎,猫头虎技术团队创始人,也被大家称为猫哥。我目前是COC北京城市开发者社区主理人、COC西安城市开发者社区主理人,以及云原生开发者社区主理人,在多个技术领域如云原生、前端、后端、运维和AI都具备丰富经验。
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用方法、前沿科技资讯、产品评测、产品使用体验,以及产品优缺点分析、横向对比、技术沙龙参会体验等。我的分享聚焦于云服务产品评测、AI产品对比、开发板性能测试和技术报告。
目前,我活跃在CSDN、51CTO、腾讯云、阿里云开发者社区、知乎、微信公众号、视频号、抖音、B站、小红书等平台,全网粉丝已超过30万。我所有平台的IP名称统一为猫头虎或猫头虎技术团队。
我希望通过我的分享,帮助大家更好地掌握和使用各种技术产品,提升开发效率与体验。
作者名片 ✍️
部分专栏链接
:
🔗 精选专栏:
InsecureRequestWarning
:
requests
库(或底层的 urllib3
)默认会尝试用 certifi
提供的 CA 根证书来验证对方证书。requests
会因为“证书校验失败”而报错/抛出异常。requests
支持一个参数 verify=False
,用于跳过证书验证。但同时也会触发 InsecureRequestWarning
警告,提醒你“这很不安全”。⚠️ 强烈建议仅在开发/测试环境、或者你已经完全了解风险并能够接受时使用下面策略,切勿在生产环境使用。
将 urllib3
的 InsecureRequestWarning
置为忽略状态。常见做法:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import requests
response = requests.get('https://47.113.219.226/api/xxx', verify=False)
print(response.status_code, response.text)
verify=False
)只针对某一次请求关闭验证,示例:
import requests
response = requests.get(
'https://47.113.219.226/api/data',
verify=False # 关闭证书验证,直接忽略警告(若不关闭,仍会抛出 InsecureRequestWarning)
)
print(response.status_code)
urllib3.disable_warnings()
(参见 3.1)。
核心思路:始终让客户端对服务器证书进行校验,并且让服务器呈现一个“可信任的、证书链完整的” TLS 证书。本文将分层介绍不同场景下的具体做法。
ssl
模块)在默认情况下也会去调用系统的信任存储来验证服务器证书,无需额外操作。假设目标服务器为 https://47.113.219.226
,已经绑定了一个由公认 CA(如 Let’s Encrypt、DigiCert)签发的证书,并且 IP 地址与证书中 Subject Alternative Name (SAN)
匹配。那么直接在 Python 中写:
import requests
response = requests.get('https://47.113.219.226/api/info')
print(response.status_code, response.json())
即可完成证书校验,不需要额外传入 verify
参数;如果服务器证书链完整且根 CA 在操作系统信任库中,就不会出现警告,也不会因证书验证失败而抛出异常。
注意:
47.113.219.226
),但绝大多数 CA 不会给纯 IP 签发公开证书(Let’s Encrypt 也仅支持域名)。certifi
库维护信任根requests
默认会调用 certifi.where()
所指向的 CA 根证书集合文件。certifi
的独立集合,确保跨平台一致性。requests
所使用的 CA 根文件路径import certifi
print(certifi.where())
# 例如输出:/usr/local/lib/python3.9/site-packages/certifi/cacert.pem
requests
(通常会一同安装 certifi
),默认 requests.get()
会使用 certifi.where()
返回的 .pem
文件来做验证。certifi
的 CA 根如果想显式地让某个请求使用 certifi
而不是系统的信任库,可以这样写:
import requests
import certifi
response = requests.get(
'https://47.113.219.226/api/data',
verify=certifi.where()
)
print(response.status_code)
requests
未启用系统存储),可以考虑此方案。
certifi
的 cacert.pem
中,操作相对麻烦。当服务器使用了自签名证书,或者是公司内部 PKI 签发的私有 CA 时,客户端需要拿到该 CA 根证书,然后在请求时显式指定。常见步骤如下。
自签名:如果服务器使用的是自签名证书(.crt
或 .pem
格式),请确认服务端将该证书完整地暴露给客户端或通过私有存储分发。
私有 CA:如果是公司内部 PKI 签发,则需要同时提供“根证书 CA.pem”+可能的“中间证书 intermediate.pem”。
合并成单一 PEM 文件:
如果有根 CA 和中间 CA,可以使用以下命令把它们合并成一个 company_ca_bundle.pem
:
cat intermediate.pem root_ca.pem > company_ca_bundle.pem
确保顺序是“中间 CA 在前、根 CA 在后”。
verify=CA_BUNDLE_PATH
Python 代码示例:
import requests
# 假设 company_ca_bundle.pem 已经放在当前目录
CA_BUNDLE_PATH = './company_ca_bundle.pem'
response = requests.get(
'https://47.113.219.226/api/secure',
verify=CA_BUNDLE_PATH
)
print(response.status_code, response.text)
requests
会使用指定的 company_ca_bundle.pem
验证服务器端证书。requests.exceptions.SSLError
。certificate verify failed
/ SSLCertVerificationError
openssl s_client -connect 47.113.219.226:443 -showcerts
来查看服务端提供的证书链,核对 CA 链是否与本地 company_ca_bundle.pem
完全一致。urllib3.disable_warnings()
+verify=False
(仅测试环境)。在企业内部或者本地测试环境,往往会自己搭建私有 CA 并对各服务签发自签名证书。以下是一整套流程示例,涵盖“如何从零开始生成自签名 CA → 签发服务器证书 → 在客户端配置验证”。
生成根 CA 的私钥与自签名根证书
# 生成根 CA 私钥(2048 位 RSA)并加密(可选密码,这里示例无密码)
openssl genrsa -out rootCA.key 2048
# 生成根 CA 证书(自签名),有效期10年
openssl req -x509 -new -nodes -key rootCA.key \
-sha256 -days 3650 \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany Ltd./OU=IT/CN=MyCompany Root CA" \
-out rootCA.pem
生成服务器端私钥及证书签发请求(CSR)
# 生成服务器私钥
openssl genrsa -out server.key 2048
# 生成 CSR(此处以 IP 举例;如果只用域名,CN 写域名即可,同时要给 SAN 配置 IP)
openssl req -new -key server.key \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany Ltd./OU=IT/CN=47.113.219.226" \
-out server.csr
# 生成一个 v3 配置文件,使证书中包含 Subject Alternative Name(SAN)
cat > server_ext.cnf <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
IP.1 = 47.113.219.226
DNS.1 = 47.113.219.226
# 如果你想同时支持域名访问,可在下面加上
# DNS.2 = example.mycompany.com
EOF
使用根 CA 签发服务器证书
openssl x509 -req -in server.csr \
-CA rootCA.pem -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 825 -sha256 \
-extfile server_ext.cnf
rootCA.srl
:记录了根 CA 的序列号,用于后续签发。server.crt
:由根 CA 签名的服务器证书,可用于部署到 Nginx/Apache 等。将 rootCA.pem
(根 CA 证书)分发给所有需要信任该证书的客户端
rootCA.pem
(或与 intermediate.pem
合并后的 bundle.pem
)放到某个固定路径。verify
指定 CA_BUNDLE”相同。server {
listen 443 ssl;
server_name 47.113.219.226;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 如果有中间证书,则使用 cat 合并:cat intermediate.crt >> server.crt
# ssl_certificate /etc/nginx/ssl/server_fullchain.crt;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://127.0.0.1:8080;
# ... 其他代理配置
}
}
server.crt
,但并没有把中间证书/根证书链一起拼接在一起,就会出现“证书链不完整”导致校验失败。server.crt + intermediate.pem + rootCA.pem
合并为 fullchain.pem
,在 ssl_certificate
中一起提供。/usr/local/share/ca-certificates/
并执行 update-ca-certificates
)。FROM python:3.9-slim
# 将自签名根证书复制到镜像
COPY rootCA.pem /usr/local/share/ca-certificates/rootCA.crt
# 更新系统信任证书存储
RUN apt-get update && \
apt-get install -y ca-certificates && \
update-ca-certificates
# 安装 Python 依赖
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r /app/requirements.txt
# 复制应用代码
COPY . /app/
WORKDIR /app/
CMD ["python", "app.py"]
此时,容器内的 Python 程序发起的所有 HTTPS 请求都会默认信任 rootCA.pem
。
虽然本博客重点关注 Python requests
抛出的 InsecureRequestWarning
,但其他语言或工具也会提示类似警告,原理相通:缺少可信 CA 或验证逻辑被关闭。下面列出常见环境下的对策,仅供参考。
curl
命令行下的 TLS 验证禁止验证:
curl -k https://47.113.219.226/api/test
# 或者等同于 --insecure
此时 curl
不会验证服务器证书,风险同样存在。
指定 CA 证书:
curl --cacert /path/to/company_ca_bundle.pem https://47.113.219.226/api/test
如果有中间证书,则同样要把它们“cat”到一起形成一个 bundle。
指定证书目录(将证书放到目录内):
curl --capath /etc/ssl/company_ca/
capath
要求该目录下的 CA 证书都必须是 .crt
文件,并且以哈希命名。
验证常见错误:
curl: (60) SSL certificate problem: unable to get local issuer certificate
通常是缺少中间链/根证书。curl: (51) SSL: certificate subject name ‘47.113.219.226’ does not match target host name ‘example.com’
则是 IP/域名不匹配。axios
、request
)下的类似警告axios
默认会继承 Node.js 的 TLS 验证策略。
关闭验证(不推荐,仅测试环境):
const axios = require('axios');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// 或者针对单个请求:
axios.get('https://47.113.219.226/api/data', { httpsAgent: new require('https').Agent({ rejectUnauthorized: false }) })
.then(resp => console.log(resp.data))
.catch(err => console.error(err));
警告:一旦
NODE_TLS_REJECT_UNAUTHORIZED=0
,将全局关闭所有 HTTPS 请求的验证,极度不安全。
使用自定义 CA:
const fs = require('fs');
const https = require('https');
const axios = require('axios');
const ca = fs.readFileSync('./company_ca_bundle.pem');
const agent = new https.Agent({
ca: ca, // 用自签名/私有 CA 进行验证
keepAlive: true,
});
axios.get('https://47.113.219.226/api/data', { httpsAgent: agent })
.then(resp => console.log(resp.data))
.catch(err => console.error(err));
request
(已废弃,但仍有项目在用)
request({ url: 'https://47.113.219.226', strictSSL: false }, callback);
request({ url: 'https://47.113.219.226', ca: fs.readFileSync('./company_ca_bundle.pem') }, callback);
HttpClient
、OkHttp
)下的 TLS 验证配置Apache HttpClient(4.x)
关闭验证(不推荐,仅测试):
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[] { new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
}}, new SecureRandom());
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.build();
HttpGet get = new HttpGet("https://47.113.219.226/resource");
CloseableHttpResponse resp = httpClient.execute(get);
上述代码将完全信任任何证书,极其危险。
使用自定义 TrustStore
:
将根 CA 导入到一个 JKS 或 PKCS12 格式的信任库:
keytool -importcert -alias mycompanyca -file rootCA.pem -keystore truststore.jks -storepass changeit
在代码中加载该 truststore.jks
:
KeyStore trustStore = KeyStore.getInstance("JKS");
try (FileInputStream instream = new FileInputStream(new File("truststore.jks"))) {
trustStore.load(instream, "changeit".toCharArray());
}
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(trustStore, null)
.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext, new DefaultHostnameVerifier());
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
使用 httpClient
发起请求即可完成 SSL 验证。
OkHttp(Square 出品)
关闭验证(仅测试):
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getUnsafeSSLContext().getSocketFactory(), getUnsafeTrustManager())
.hostnameVerifier((hostname, session) -> true)
.build();
(getUnsafeSSLContext()
与 getUnsafeTrustManager()
返回一个信任所有证书的 SSLContext
与 TrustManager
,同样危险。)
使用自定义 CA:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new FileInputStream("company_ca.pem");
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager)tmf.getTrustManagers()[0])
.build();
http.Client
TLS 配置Go 默认会使用系统根证书来验证,如果服务器证书有效,一般不需要额外配置。
关闭验证(仅测试):
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://47.113.219.226/api")
加载自定义根证书:
caCert, err := ioutil.ReadFile("company_ca.pem")
if err != nil {
log.Fatalf("Unable to read CA cert: %v", err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
RootCAs: caCertPool,
}
transport := &http.Transport{TLSClientConfig: tlsConfig}
client := &http.Client{Transport: transport}
resp, err := client.Get("https://47.113.219.226/api")
HttpClient
)下的 Server Certificate 验证关闭验证(仅测试):
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
var client = new HttpClient(handler);
var response = await client.GetAsync("https://47.113.219.226/api");
使用自定义根证书:
将根证书以 PEM/DER 格式加载,转换为 X509Certificate2
:
X509Certificate2 caCert = new X509Certificate2("company_ca.cer");
var handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
// 在链的根位置插入自定义 CA
chain.ChainPolicy.ExtraStore.Add(caCert);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
bool isValid = chain.Build(cert);
// 也可以检查 cert.Subject 是否匹配或检查 cert.Issuer 匹配
return isValid;
};
var client = new HttpClient(handler);
var response = await client.GetAsync("https://47.113.219.226/api");
在不同环境切换时,往往会使用不同程度的“放宽”或严格的 TLS 验证策略。以下结合实践,给出一个由浅入深的最佳实践流程。
原则:尽量保持与生产环境一致的验证流程。
推荐做法:搭建一个由“私有 CA”签发的测试证书
certifi
中追加、或通过 Python 指定 verify
)。如果确实需要临时关闭验证
一定要在代码中显著标注:例如在 request 前后写好注释 # TODO: 仅测试环境关闭验证,发布请移除
。
不要直接修改全局抑制;尽量用内联 verify=False
限定作用域:
# NOTE: 本行仅在 dev 环境下关闭验证,prod 环境需要改为 verify='/path/to/ca.pem'
response = requests.get(url, verify=False)
始终保持私有 CA 的私钥/根证书文件在版本管理之外,不要泄露。
staging.example.com
),让 DNS 指向测试服务器 IP。certbot
申请 Let’s Encrypt 证书,自动续期。requests
默认即可)来校验证书。certbot
(或其它 ACME 客户端)自动申请和续期。
certbot certonly --webroot -w /var/www/html -d example.com
certbot renew --post-hook "systemctl reload nginx"
verify=
的麻烦。https://example.com
的证书有效期;timeout=5
秒之类),避免请求长期挂起。在排查 InsecureRequestWarning
及其它 TLS/SSL 相关错误时,可以参考以下思路和工具。
openssl s_client
或 openssl x509
验证服务器证书检查服务器收益到的证书链
openssl s_client -connect 47.113.219.226:443 -showcerts
If the server sends only its leaf certificate but没有中间 CA,则会看到类似:
depth=0 CN = 47.113.219.226
verify error:num=20:unable to get local issuer certificate
你需要等到输出完整链信息后,确保包含“中间证书”与“根证书”。
提取并转换服务器的 Leaf Certificate
openssl s_client -connect 47.113.219.226:443 -showcerts </dev/null 2>/dev/null \
| openssl x509 -outform PEM > server_leaf.pem
然后可以进一步:
openssl x509 -in server_leaf.pem -text -noout
查看证书有效期、颁发者(Issuer)、签发者(Subject)、SAN 列表等信息。
特别关注:
Not Before
/ Not After
:证书是否过期Issuer
是否为你所信任的 CASubject Alternative Name
是否包含 IP 或域名验证本地手头的 CA 文件是否能验证服务器证书
openssl verify -CAfile company_ca_bundle.pem server_leaf.pem
server_leaf.pem: OK
,说明用这个 CA bundle 能通过对 Leaf 证书的验证。unable to get local issuer certificate
),说明你本地缺少上游某个中间证书。fullchain.pem
)。openssl s_client
确认链路。ca-certificates
包已过期,可能缺少新的根 CA;即使在本地机器能通过,切换到 CI 环境就可能“找不到根 CA”。certifi
,可以通过 pip install --upgrade certifi
来同步到最新的根 CA 列表。.pem
→ 生效。certificate has expired
。hostname "47.113.219.226" does not match "example.com"
。
verify
时额外提供一个“忽略 host 验证”的回调(例如 Node.js 里可写 .hostnameVerifier(() => true)
,但强烈不推荐在生产这样做)。openssl s_client
” 输出显示链不完整,则立刻修复。verify=False
或者全局禁用 InsecureRequestWarning
。requests
文档:Advanced Usage / SSL Warnings
certifi
官方仓库与说明
man openssl
/ https://www.openssl.org/docs/man1.1.1/crypto/tls
文档
HttpClient
HTTPS 配置示例
本文涵盖了从“最简单的抑制警告”到“企业级 TLS 证书管理”的整套思路与实践细节。希望对你研发、测试、运维各环节都能有实质帮助,让你的 HTTPS 通信既符合安全规范,又在开发效率与维护成本之间取得平衡。祝顺利解决 InsecureRequestWarning: Unverified HTTPS request
!
版权声明: 本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问猫头虎的博客首页。