该命令是curl -k。如果这确实就是你需要的全部内容,那么你可以到此为止。
本文其余部分之所以存在,是因为**-k 并不能解决证书问题——它只是关闭了发现该问题的验证机制。** curl 的官方文档对此表述得格外直白,称其“强烈建议避免这样做”,并指出在生产环境中“绝不能跳过验证”。这些强调是原文所加,并非我们所为。
我们是 Geonode,我们销售代理服务,但必须坦诚地说明:这个问题几乎与我们的产品毫无关系。证书错误发生在您的机器、其信任存储库与服务器之间。 只有一个例外——企业网络中的拦截代理是导致此错误的最常见原因之一,下文对此有专门说明——但无论您从我们还是其他任何人那里购买什么,都无法解决证书错误。
值得您花时间去做的是故障诊断。 证书错误的具体原因寥寥无几,对于其中大多数情况,只需输入 -k 并保持验证功能开启,就能在几乎相同的时间内解决问题。 缺失的中间证书、过期的 CA 证书包、curl 未识别的企业根证书、已过期的证书、主机名不匹配。 每种情况都有其独特的特征和对应的解决方案。
习惯性地使用 -k 带来的实际风险并非抽象概念。风险在于:该标志会被复制到脚本中,脚本投入生产环境后,原本能检测到真实拦截问题的检查机制便不再运行——而在那个环境中,可能早已无人记得它曾被关闭过。
下文关于 curl 行为的所有内容均来自 curl 自身的 SSL 证书文档 和《curl 指南》。
大家期待已久的命令
这里就是它,附带各种变体。
# Skip verification of the server's certificate
curl -k https://example.com
# Same thing, long form
curl --insecure https://example.com
# Skip verification for the proxy's certificate as well
curl -k --proxy-insecure -x https://proxy.example.com:8080 https://example.com
、
-k
和 --insecure
都是同一个选项。--proxy-insecure
则是独立的选项,它针对的是 HTTPS 代理自身的证书,而非目标站点的证书——这两者是两项独立的验证,禁用其中一项不会影响另一项。
在屏蔽错误前先查明原因
在启用该标志之前,请花十秒钟查明实际出了什么问题:
curl -v https://example.com
详细输出会显示 TLS 握手过程以及验证失败的具体原因。 “无法获取本地颁发者证书”与“证书已过期”是截然不同的问题,而这两者又与主机名不匹配不同——每种情况所需的解决方法也各不相同。
更深入地查看服务器实际提供的证书:
curl -vI https://example.com 2>&1 | grep -A 20 "Server certificate"
这会显示主题、签发者和有效期。通常答案一目了然:证书上周二已过期,或者它是为另一个主机名签发的,又或者签发者是一个你从未听说过的组织——这通常意味着有某种东西正在拦截你的流量。
值得养成的习惯
先诊断,再决定。-k
是一款用于对您控制的服务器进行一次性测试的合法工具。但当它成为一种条件反射而非最终结论时,就会成为问题,因为该命令行不会提示它屏蔽了哪些内容。
-k 选项实际上会禁用什么
其作用比大多数人想象的要广泛,这一点值得深入理解。
根据 curl 的文档,默认情况下,curl 通过“验证签名并确保证书是针对 URL 中提供的服务器名称签发的”来执行证书验证。
这句话描述了两项独立的检查,而 -k 会同时禁用这两项检查。
检查一:签名链
该证书的签名链是否可追溯至系统信任的证书颁发机构?这正是证明该证书是由公认的证书颁发机构签发,而非任何安装了 OpenSSL 并花五分钟时间的人自行生成的依据。
如果没有这一验证,**任何证书都会被接受。**无论谁在您与服务器之间创建的自签名证书,都能像来自真实证书颁发机构的证书一样轻松通过验证。
检查二:主机名
该证书是否确实属于您请求的主机? 一张针对 attacker.example 有效且正确签发的证书,并不等同于 bank.example 的证书,而主机名验证正是确保这一点的关键。
如果没有主机名验证,针对任何域名的真实证书都将被用于任何连接。
剩余部分
连接仍然是加密的。 TLS 仍会协商加密套件,且网络传输中的数据并非明文。
但缺乏身份验证的加密只能保护你免受被动监视者的侵害,而无法抵御主动攻击者。《curl 指南》明确指出了其后果:一旦验证强度降低,“你的通信可能会遭受中间人攻击”。 任何能够拦截该连接的人都可以出示自己的证书,终止您的 TLS 连接,读取并修改所有内容,然后建立一条独立的后续连接。您看到的只是挂锁图标所代表的加密状态,却完全无法理解其背后的含义。
为何在脚本中这更重要
在交互式操作中,您知道自己输入了 -k,也清楚这样做的原因。 而在脚本中,该标志会长期存在,即便最初的理由早已被遗忘。凭据会通过它发送,数据也会通过它返回并被视为可信。
如果你必须在自动化脚本中使用它,请留下注释说明原因,以及需要做出哪些更改才能将其移除。这一行注释,正是深思熟虑的决策与隐形决策之间的区别。
为何会出现此错误
主要有五种原因,几乎涵盖了所有情况,每种原因在详细输出中都有其独特的特征。
1. 缺少中间证书
这是最常见的原因,属于服务器配置错误,而非您这边的问题。
证书链:服务器的证书由中间证书颁发机构签名,而该机构又由您系统信任的根证书签名。**服务器理应发送其自身的证书以及中间证书。**如果它只发送自身的证书,您的客户端就无法完成证书链的构建。
浏览器通常会通过自动获取缺失的中间证书或缓存之前访问时的中间证书来掩盖此问题,而 curl 不会这样做。因此,典型的症状是:一个网站在浏览器中运行完美,但在 curl 中却失败——这说明服务器确实配置错误;而浏览器只是在“宽容”地处理此问题。
**解决方法:**请让服务器管理员配置完整的证书链。如果是您的服务器,这只需五分钟即可完成,且能一次性解决所有非浏览器客户端的问题。
2. CA 证书包过期
系统的受信任证书颁发机构(CA)列表已过期,导致无法识别合法签发的证书。这种情况常见于旧系统、精简版容器镜像以及长期运行的虚拟机中。
**解决方法:**更新系统的 CA 证书包。
3. 企业级 TLS 检查
您所在组织的网络会对流量进行解密和重新加密,并出示其自身的证书。相关内容见下文专门章节。
4. 自签名证书
开发服务器、内部工具、专用设备。由于证书是自签名的,因此不存在可锚定的证书颁发机构。
**解决方法:**使用 --cacert 显式指定该证书供 curl 使用,而不是全局禁用验证。
5. 证书确实已过期或错误
有时检查结果是正确的。证书已过期,或证书是针对其他主机名签发的,或者该网站确实配置错误。
**解决方法:**通知网站管理员。此时使用 -k 意味着故意忽略准确的信息。
解读详细输出
“无法获取本地颁发者证书”指向原因 1、2 或 3。“证书已过期”属于原因 5,无需解释。主机名不匹配也属于原因 5。不熟悉的颁发者名称属于原因 3,值得深入调查,而非仅通过变通方式解决。
按优先级排序的解决方案
从最佳到最差。依次尝试,一旦某项有效即停止。
1. 修复服务器
如果问题是中间证书缺失,且您控制着服务器,请配置完整的证书链。 这将永久性地为所有客户端解决问题,也是唯一能同时改善其他用户体验的解决方案。
2. 更新您的 CA 证书捆绑包
# Debian / Ubuntu
sudo apt-get update && sudo apt-get install --reinstall ca-certificates
# RHEL / Fedora
sudo update-ca-trust
# Alpine
apk add --no-cache ca-certificates && update-ca-certificates
最后这一项解决了容器内部绝大多数证书错误,因为精简版镜像通常根本不包含 CA 证书捆绑包。
3. 让 curl 指向正确的证书
对于自签名或内部证书,请显式提供。验证功能仍会保持启用——你只是告诉 curl 在此连接中应信任什么。
# A specific CA certificate file
curl --cacert /path/to/ca.crt https://internal.example.com
# A directory of certificates
curl --capath /etc/ssl/certs https://internal.example.com
这是针对内部服务和开发环境的正确解决方案,其操作难度仅比 -k
稍大一些。区别在于,如果出现意外情况拦截了连接,该方法仍会失败——而这正是进行证书验证的全部意义所在。
4. 在环境中设置
对于整个会话或容器,curl 的文档指出,你可以“通过将环境变量 CURL_CA_BUNDLE
设置为任意路径来指定自己的 CA 证书文件”,并且“也支持SSL_CERT_FILE
和 SSL_CERT_DIR
”。
export CURL_CA_BUNDLE=/path/to/corporate-ca-bundle.crt
curl https://example.com # verification on, using your bundle
后两个变量也被其他众多工具所支持,这使得这种方法能够一次性修复整个环境,而无需逐个工具进行配置。
5. 将证书添加到系统存储库
对于会频繁用到的企业根证书,请在系统级别一次性安装。这样,该机器上的所有工具都能正常运行。
# Debian / Ubuntu
sudo cp corporate-root.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
6. 只有在此之后,才使用 -k
如果确实需要使用,请将其作用范围控制得尽可能窄:仅限一条命令、一台主机,并附上注释说明原因。
企业级 TLS 检查
这是让人们感到最困惑的原因,因为表面上似乎没有任何问题。
实际情况
许多组织出于安全和合规目的,运行着用于检查加密流量的设备。要读取 TLS 流量,必须对其进行解密——这意味着终止您的连接、检查内容,然后建立自己的连接继续传输。
为了让这一过程顺利进行且不引发浏览器报错,企业会在受管设备上安装自己的根证书。浏览器和系统应用程序信任该证书,因此一切看起来都很正常。该检测设备正在执行的正是证书验证机制本应检测的拦截行为,而且它已被授权这样做。
为何 curl 仍会报错
根据 curl 的构建方式及其使用的 TLS 库不同,它可能不会使用系统信任存储库。因此,您的浏览器会无条件接受的企业根证书对 curl 而言是未知的,curl 会正确地报告无法验证证书链。
线索就在详细输出中:证书的签发者是您所在的组织,或是某家安全厂商的名称,而非公共证书颁发机构。
正确的解决方法
导出企业根证书——可由 IT 团队提供,或从浏览器中提取——并使用上述方法之一将其添加。若使用 shell 会话,请访问 CURL_CA_BUNDLE;若为日常使用的机器,请使用系统存储库。
这样可以确保验证功能正常工作。您只是信任了组织刻意安装的额外一个证书颁发机构,而非无条件信任所有证书。
错误的解决方法
在所有地方永久启用 -k。虽然这确实有效,但这意味着您将无法察觉真正的恶意拦截,因为您已禁用了唯一能提醒您的机制。在已经存在合法拦截的网络中,区分合法与非法拦截正是值得保留的能力。
关于容器的说明
容器不会继承主机的信任存储库。一个在您的笔记本电脑上运行正常但在企业环境中失败的容器,通常需要在镜像中添加企业根证书或将其挂载进去——这属于构建时的修复方案,而非在 Dockerfile 中禁用验证的理由。
代理和 TLS
这是我们的专业领域,主要在于确定引发投诉的具体证书是哪一个。
两项独立验证
当您通过 HTTPS 代理路由 curl 请求时,会建立两条 TLS 连接,并进行两项独立的验证。
你与代理之间的连接。 由 --proxy-insecure、--proxy-cacert 及相关选项控制。
与目标服务器之间的连接。 由 -k、--cacert 以及常规选项控制。
混淆这两者是导致时间浪费的常见原因。 如果在配置了代理的情况下 curl 报证书错误,请在进行任何更改之前先确定是哪条连接出了问题——详细输出会将它们区分开来。
普通 HTTP 代理不会导致此问题
当使用标准 HTTP 代理且目标为 HTTPS 时,curl 会发出 CONNECT 请求,代理会打开一条隧道。 随后,TLS 握手将在您与目标服务器之间端到端进行,而 代理无法查看或修改证书。如果您通过此类代理收到证书错误,原因在于前面提到的常见问题之一,而非代理本身。
这也是普通代理无法读取您的 HTTPS 流量的原因。它只是传输加密的字节,却无法解读其内容。
拦截型代理则会
如果代理被配置为检查 HTTPS,它会终止 TLS 连接并出示自己的证书——这其实就是上文提到的企业检查案例,只是换了种形式。 其特征相同:详细输出中会出现一个陌生的签发者。
解决方法
应将代理的证书颁发机构(CA)加入受信任列表,而非禁用证书验证。理由与之前相同,操作步骤也一样。
还有一个比证书更重要的相关要点:请检查您的主机名查询是通过代理进行,还是在本地解析。使用 curl 时,socks5h:// 会将主机名发送给代理,而 socks5:// 则在您的机器上进行解析。这虽不是证书问题,但这是代理配置中常见的另一种与所有者意图不符的情况。
在代码中而非使用 curl
每种编程语言都会面临同样的抉择,而且通常默认的易用性更差。
Python
import requests
# Verification on, using a specific CA bundle — preferred
r = requests.get("https://internal.example.com", verify="/path/to/ca.crt")
# Verification off — equivalent to curl -k
r = requests.get("https://internal.example.com", verify=False)
requests
在验证功能被禁用时会发出警告,而互联网上大量代码选择抑制该警告,而非解决根本问题。抑制警告比原始问题本身更糟糕,因为这会消除最后一个提示异常情况的信号。
requests
它还尊重 REQUESTS_CA_BUNDLE
,且 SSL_CERT_FILE
在整个 Python 生态系统中被广泛采用——因此前面提到的环境变量方法通常能一次性修复整个工具链。
Node.js
// Preferred: supply the CA for this request
const https = require('https');
const fs = require('fs');
const agent = new https.Agent({ ca: fs.readFileSync('/path/to/ca.crt') });
// Avoid: disables verification for the entire process
// NODE_TLS_REJECT_UNAUTHORIZED=0
该环境变量值得特别警告。 它是全局的,因此会禁用该进程建立的每个连接的验证,包括你未编写的连接——如依赖项、遥测和包下载。Node 自身在设置该变量时会打印警告。请改用 NODE_EXTRA_CA_CERTS
来添加证书;这样既能解决实际问题,又不会产生范围上的副作用。
模式
每个生态系统都同时提供针对性选项和全局开关。针对性选项虽然多花几个字符,但能确保对所有未被明确豁免的情况保持验证功能。
而全局开关往往会被放入代码库,被复制到其他三个服务中,并在两年后被某人发现——当时他正试图查明为何系统故障未被检测到。
何时“忽略”其实也没关系
这并非一章关于“禁止”的内容——确实存在某些特殊情况,若对此视而不见,人们很容易就忽视这些建议。
**你自己启动的本地开发服务器。**你完全清楚另一端是什么,因为它就在你自己的机器上。-k 访问 localhost 并不存在实质性风险。通过 --cacert 提供证书依然更整洁,而且只需几秒钟。
一次性的交互式测试,用于调试证书本身。 在其他地方修复证书问题时,确定请求的其余部分是否正常工作,这是一种完全合理的用法。
一个由你端到端控制的隔离网络,其中不存在任何可供拦截器驻留的路径。这种情况在实际中很少见,值得你诚实地审视一下它是否真的存在。
针对临时测试基础设施的自动化测试,其中使用的是不断重新生成的自签名证书。即使在此情况下,通常也能且更应直接针对证书进行验证。
何时不可行
任何生产环境中的操作。 curl 的官方文档明确指出绝不能这样做,这是正确的。
任何涉及凭据传输的情况。 未经验证,你无法确定接收方是谁。
任何需要根据响应采取行动的情况。 未经验证,响应内容可能由传输路径中的任何人编写。
任何位于你无法控制的网络上的场景——公共 Wi-Fi、客户的办公室、共享环境。
**作为反复出现错误的永久性解决方案。**反复出现的错误有其原因,而原因是有解决办法的。-k 是一种逃避查明原因的做法。
一行测试法
自问:如果此刻有人正在拦截这个连接,我是否希望知道?
如果是,请保持验证功能开启并修复根本原因。如果答案确实是否定的——例如向你桌上的机器发送的一次性请求——那么使用 -k 即可,而这篇文章已经比你需要的要长了。
大家还问
如何在 curl 中忽略 SSL 证书错误?
curl -k https://example.com, 或完整写法 --insecure。对于 HTTPS 代理自身的证书,请使用 --proxy-insecure。curl 的文档强烈建议避免这样做,并且绝不要在生产环境中使用。
curl -k 实际上起什么作用?
它会禁用两项检查:证书是否可追溯至受信任的证书颁发机构,以及证书是否为所连接的主机名签发。连接仍保持加密状态,但不再经过身份验证,这意味着连接内容可能遭到无法被检测到的拦截。
为什么我的浏览器没有报错,而 curl 却报证书错误?
通常是因为服务器未发送其中间证书。浏览器通常会自动获取或缓存缺失的中间证书;而 curl 不会。这是因为服务器配置有误,而浏览器对此较为宽容。也可能是您的浏览器信任某个企业根证书,而 curl 并不了解该证书。
如何修复“无法获取本地颁发者证书”的问题?
按以下顺序操作:让服务器发送完整的证书链;更新系统的 CA 证书包;或者通过 --cacert 命令让 curl 指向正确的证书。在容器环境中,这通常只是缺少了 ca-certificates 包。
如何让 curl 信任自签名证书?
使用 curl --cacert /path/to/cert.crt https://internal.example.com. 验证将保持启用状态,curl 会信任该连接中使用的特定证书,这比使用 -k 更安全,且操作步骤仅略多一点。
能否为所有 curl 命令设置一个 CA 证书包?
可以。将 CURL_CA_BUNDLE 设置为您的证书包路径。curl 还支持 SSL_CERT_FILE 和 SSL_CERT_DIR,许多其他工具也支持这些选项,因此这是一次性修复整个环境的有效方法。
使用 curl -k 是否仍会加密我的流量?
是的,连接仍然是加密的。但仅加密而不进行身份验证,只能防止被动监视。主动拦截者可以出示任何证书,解密所有内容并将其转发出去——而验证机制的存在正是为了防止这种情况。
在 Docker 容器中使用 curl -k 安全吗?
通常没有必要。精简的基础镜像通常不包含 CA 证书包,因此安装 ca-certificates 即可正确解决此问题。如果问题源于企业根证书,请将其添加到镜像中或挂载进来,而不是禁用验证。
总结 输入 `
`curl -k`` 只需按一下键盘,而且确实有效。它的作用是关闭了提示你“出了问题”的机制,但原本存在的问题依然存在。
请先花十秒钟访问 curl -v。证书错误通常有几种常见原因,每种原因都会明确提示。 中间证书缺失是迄今为止最常见的原因,这也解释了那种令人困惑的情况:某个网站在浏览器中能正常访问,但在 curl 中却失败——浏览器会自动获取缺失的部分,而 curl 不会,且服务器确实配置有误。在容器环境中,问题往往仅仅是缺少了 ca-certificates 这个包。
当你需要信任一些不寻常的证书时,请明确指定信任对象。例如,针对单次连接使用 --cacert,针对整个环境使用 CURL_CA_BUNDLE 或 SSL_CERT_FILE,而对于日常使用的机器,则使用系统存储。这些方法既能保持验证机制正常运行,又能向 curl 明确告知你决定接受的那个额外证书颁发机构。 与 -k 相比,这些操作仅多耗费几秒钟,但如果路径中出现意外情况,它们仍会失败——这正是验证机制存在的全部原因。
在企业网络中,问题通常源于 TLS 检查——解决方法是添加该组织的根证书,而不是停止验证。 在已经实施授权拦截的网络中,能够区分授权拦截与未经授权的拦截,其价值比平时更高,而非更低。
代理服务器在此处基本上是个红鲱鱼。普通的 HTTP 代理仅对 HTTPS 进行隧道传输,无法触及证书;如果你通过代理看到错误,原因在于其他地方。 我们销售代理服务器,但针对此问题我们没有任何产品可向您推荐。
而且,如果对“我是否希望知道此刻是否有人正在拦截此请求”这一问题的回答是否定的——例如,这是发往您自己办公桌上服务器的临时请求——那么启用 -k 即可。造成危害的正是这种条件反射,而非该标志本身。