一个多礼拜没出现过的老问题,这两天又回来了。用户访问站点时 Chrome 直接拦在页面前,红色警告界面显示 NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM,换成 Edge、Firefox 也差不多,只是措辞不同。这类报错最容易出现在老服务器、自建 CA 签发证书、或者长期没换过证书的对外系统上。大多数人的第一反应是“证书是不是过期了”,查一下发现有效期还长;再不然以为浏览器抽风,清缓存、重装都没用。实际上问题出在证书的签名算法上——你还在用 SHA-1,而现代浏览器从 2020 年前后就开始动手清理这种弱算法了。
说白了,NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM 就是你网站的数字证书签名字段用了被淘汰的哈希算法,浏览器认为这类证书无法再为身份真实性背书,于是直接不信任。这不是配置问题,也不是服务器能不能打开的问题,想绕过去只能换掉证书本身。这篇博文我会从错误原理讲起,再把“怎么判断、怎么换、怎么验、踩了哪些坑”完整梳理一遍,适合正在被这个报错折磨的运维、自建站站长,还有被内网系统绑定在旧证书里的同学参考。
1. 先搞懂这个错误到底在说什么
1.1 为什么浏览器突然“翻脸不认人”
HTTPS 证书里有两类算法容易被混在一起。一类是公钥算法,比如 RSA、ECDSA,它决定密钥交换和身份验证的数学体系;另一类是签名哈希算法,比如 SHA-1、SHA-256,它负责对证书内容做摘要并让 CA 签名。我们平时说的“证书签名算法”一般指后者,而 NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM 针对的就是这个哈希算法。
SHA-1 是 1995 年发布的算法,碰撞攻击成本这些年一路走低,到了 2017 年 Google 已经用实际攻击演示了 SHA-1 碰撞,业界共识变成“必须淘汰”。Chrome 从 2017 年开始逐步弱化 SHA-1 证书信任,到了 Chrome 56 以后,对 2016 年之前签发的 SHA-1 证书基本就不认了;Firefox 也在同一时期跟进。关键在于,浏览器不是看你证书有效期是不是还剩一年,而是看你证书“签发时”用的什么签名算法——只要哈希部分是 SHA-1,不管证书哪天到期,一律按失效处理。
更麻烦的是,这个报错和证书过期、域名不匹配、CA 不受信任都不太一样。普通证书错误用户还能点“继续前往”,但 SHA-1 签名算法在 Chrome 中被归类为“严重错误”,默认不提供直接绕过的入口,用户除非在 Linux 启动参数里加 --ignore-certificate-errors 之类的开关,否则页面根本进不去。对普通用户来说,这个错误约等于“这个网站不可信”。
1.2 什么情况下容易踩到这个坑
我见过几类高频场景,确认一下你有没有中招:
- 老业务服务器,证书是 2015、2016 年签发的,中间一直没到期,所以没人动过,浏览器策略升级后突然就大面积报错。
- 企业内部系统自建 CA 签发的证书,CA 根证书和服务器证书全用默认配置生成,很多自建 CA 的默认摘要算法就是 SHA-1,于是一大批内网站点全军覆没。
- 某些硬件设备、堡垒机、监控大屏自带 HTTPS 管理页面,证书固化在固件里,厂商发布的版本比较老,同样卡在 SHA-1。
- 使用了某些商业证书,但证书链里的中间证书是 SHA-1 签发的,证书本身没问题,链中间那截出问题也会触发同类报错。
需要注意的是,NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM 有时候并不是服务器证书的问题,而是证书链里某一级 CA 证书的问题。所以排查时不要只看服务器证书那一层,得把证书链完整过一遍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体修复思路:换算法,而不是“哄”浏览器
2.1 为什么不能直接改配置解决
签名算法是 CA 在签发证书时写入证书结构里的固定字段,不是服务器配置里可以动态改的开关。你打开 example.crt 文件,看到 Signature Algorithm: sha1WithRSAEncryption 这一行,它表明的就是一张证书的“身份元数据”。除非重新签发一张使用 SHA-256 签名的证书,否则服务器端再怎么调 SSL 配置、改协议版本、换加密套件,都无法让这张证书的签名算法变成 SHA-256。
我经常看到有人把问题归到 Nginx 配置里的 ssl_ciphers,反复加 HIGH:!aNULL、!MD5 之类的选项,折腾一晚上,最终花一两分钟重新签张证书就解决了。这里要分清:ssl_ciphers 控制的是 TLS 握手时协商的对称加密和密钥交换算法,而证书签名算法属于“CA 签发给你的那份凭证”的完整性计算方式。两者层级不同,改前者不可能影响后者的判定结果。
同样的道理,把服务器系统时间调回几年、关掉浏览器的“自动检测设置”、添加受信任站点之类的操作也都是在错误的方向上使劲。浏览器只要读证书字段,发现签名算法是 SHA-1,就会给出弱算法错误,和时间、系统本身没关系。
2.2 修复方案的三种路线
按路径选型,实际操作基本分三类:重新申请公网可信证书、重新签发内网自建 CA 证书、临时绕行验证。前两种是正常解法,第三种只适合做临时评估。
路线 A:向云厂商或者 Let’s Encrypt 申请新证书。
这是最直接的方式,只要域名能正常解析、能验证归属权,几分钟就能拿到 SHA-256 签名的证书。阿里云、腾讯云、华为云都有免费 DV 证书,Let’s Encrypt 更不用说,90 天有效期配自动化续期,对个人站和小企业是性价比最高的方案。如果域名已经用了某家 CDN 或云负载均衡,直接从控制台申请并一键部署就行。
路线 B:自建 CA 重新签发证书。
内部系统如果必须要用自己的 CA,那就需要把 CA 的根证书、中间证书、服务器证书全部升级到 SHA-256。具体做法是用 OpenSSL 重新生成 CA 密钥对,并重新创建根 CA,然后把所有依赖旧 CA 签发的服务器证书重新签一遍。如果只是服务器证书是 SHA-1,但 CA 根证书还是 SHA-256 签的,其实只要重新签服务器证书就行;反过来如果 CA 根证书本身就是 SHA-1,那整个 CA 体系都得重建,否则客户端根本没法验证。
路线 C:临时关闭浏览器拦截。
只有在排除问题时我会建议试一下,本质上没有任何安全收益。Chrome 启动时加 --ignore-certificate-errors,或者把站点加入某些浏览器的安全例外,都只是让你“能看得到页面”,但用户那边依然会被拦截,没用。生产环境不要用这种方式交付。
下面把三条路线放一起对比:
| 路线 | 适用场景 | 核心操作 | 耗时 | 风险点 |
|---|---|---|---|---|
| A 重新申请公网证书 | 有域名、对外提供服务 | 域名验证 + 签发 + 部署 | 分钟级 | 需要域名控制权;有效期短需配置续期 |
| B 自建 CA 重签 | 内网系统、设备证书 | 重建 CA / 重新签发服务器证书 | 小时级 | 客户端需信任新 CA;全部依赖证书要同步重签 |
| C 临时绕过 | 仅排查、开发调试 | 修改浏览器启动参数 | 分钟级 | 无安全价值,不解决用户侧问题 |
对于多数中小企业,如果网站已经通过 ICP 备案且使用云厂商服务,路线 A 是首选。对自建机房、无外网域名的场景,路线 B 是唯一接近合规的做法。
3. 实操:一步一步换掉弱签名算法
3.1 第一步:先确认当前证书的签名算法
动手之前先搞清楚现状。用 OpenSSL 查看证书,或者直接在浏览器里打开证书信息,看“签名算法”字段是不是 sha1WithRSAEncryption。命令行方式更精准,也方便检查证书链每一层:
bash复制openssl x509 -in yourdomain.crt -text -noout | grep "Signature Algorithm"
如果服务器证书这行输出 sha1WithRSAEncryption 或 sha1WithRSA,基本确定问题来源。但别停在这一步,还要检查证书链里每一级证书的签名算法。你可以把 CA 根证书、中间证书文件都执行同样命令;更省事的办法是直接连接线上服务看握手时的证书链:
bash复制openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null | openssl x509 -text -noout | grep "Signature Algorithm"
注意 openssl s_client 输出的是服务端实际下发的证书链,中间证书和根证书如果用 SHA-1 签名,也会暴露出来。另外还要看证书的公钥算法和位数,建议 RSA 至少 2048 位,如果发现证书还是 1024 位 RSA,哪怕签名算法是 SHA-256,也建议一并升级,因为 1024 位 RSA 本身已不被主流浏览器信任。
3.2 第二步:生成新的私钥与证书签发请求
如果走云厂商证书或 Let’s Encrypt,通常只需要提供 CSR 或者在控制台自助生成。手动生成 CSR 时,命令里显式指定 SHA-256:
bash复制openssl req -new -newkey rsa:2048 -sha256 -nodes \
-keyout yourdomain.key \
-out yourdomain.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=YourCompany/CN=yourdomain.com"
这里 -sha256 就是告诉 OpenSSL 用 SHA-256 作为签名摘要算法,生成的 CSR 提交给 CA 后,签出来的证书才会用 SHA-256。rsa:2048 指定 RSA 密钥长度 2048 位,这是当前的主流基线;如果配合 ECC 密钥,可以用 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1,不过 ECC 证书的兼容性略低于 RSA,对大多数站点来说 RSA 2048 更省心。
生成 CSR 后可以用下面命令再次确认里面记录的签名算法:
bash复制openssl req -in yourdomain.csr -text -noout | grep "Signature Algorithm"
看到 sha256WithRSAEncryption 再提交给 CA,避免签出来的证书还是老算法。
3.3 第三步:提交 CA 签发并部署到服务器
这一步取决于你的证书来源。
如果是云厂商 DV 证书,在 SSL 证书控制台提交 CSR,然后按提示做 DNS 验证或文件验证。DNS 验证就是在域名解析里加一条指定的 TXT 记录;文件验证则要求把指定文件名放到网站根目录下。验证通过后一般几分钟就能签发,下载 Nginx 或 Apache 格式的证书包。
如果是 Let’s Encrypt,用 certbot 最省事:
bash复制sudo certbot certonly --standalone -d yourdomain.com --email you@example.com --agree-tos --no-eff-email
certbot 默认就签 SHA-256 证书,而且会帮你生成私钥,到期前自动续期逻辑自带。需要强调一点:Let’s Encrypt 证书默认有效期是 90 天,务必配置自动续期,否则过三个月又会出现“证书过期”的问题——那就不是弱签名算法,而纯粹是忘了续。手动续期命令是 certbot renew,配合 crontab 每天跑两次即可。
部署到 Nginx 时,核心是配置好 ssl_certificate 和 ssl_certificate_trusted_certificate。前者放服务器证书,一般需要把中间证书和服务器证书按顺序合并成一个 .pem 文件;后者单独放 CA 链文件。一个示例:
nginx复制server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/nginx/ssl/yourdomain_fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/yourdomain.key;
ssl_trusted_certificate /etc/nginx/ssl/yourdomain_chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
}
改完配置先测试再重载:
bash复制sudo nginx -t
sudo systemctl reload nginx
Apache 类似,SSLCertificateFile 指向证书文件、SSLCertificateKeyFile 指向私钥、SSLCertificateChainFile 指向中间证书。IIS 则是在服务器证书界面导入 PFX 格式证书,绑定站点时选择新证书即可。工具差异只是外壳,核心思想一致:让服务端下发的证书链全部换成 SHA-256 签名。
3.4 第四步:验证修复是否成功
部署完别急着告诉用户“已经修好了”,至少做三层验证。
第一层,本地 OpenSSL 查看证书签名:
bash复制openssl x509 -in /etc/nginx/ssl/yourdomain_fullchain.pem -text -noout | grep "Signature Algorithm"
应该输出 sha256WithRSAEncryption。
第二层,通过线上端口实测服务端下发的证书链:
bash复制openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts 2>/dev/null | grep "Signature Algorithm"
输出里可能有多行,其中带有 Server certificate 后面那行是服务器证书的签名算法,另外还会出现证书链中各级证书的算法。所有该是 SHA-256 的位置都不能再出现 sha1WithRSAEncryption。
第三层,浏览器无痕窗口访问。最好用 Chrome 无痕模式,排除缓存干扰;如果 Chrome 正常,再用 Edge 和 Firefox 各验一遍。更严格的话还可以跑一次 SSL Labs 的在线检测,它会给出证书链、协议、算法等评分,方便你全面检查。如果第三步检查发现证书链里某个中间证书或根证书确实还是 SHA-1,即使服务器证书换了也救不了,必须把整条链一起换掉。
4. 常见卡壳问题与排查实录
4.1 换完证书后依然报错,问题多半在证书链
我在帮客户排障时不止一次遇到这种情况:服务器证书明明已经换成了 sha256WithRSAEncryption,浏览器还是提示弱签名算法错误。用 OpenSSL 一查证书链才发现,根证书或者中间证书还是老的 SHA-1 签名。云厂商签发的证书通常不会这么坑,但自建 CA 场景非常容易出现——复用的还是那套用 OpenSSL 默认参数创建的根 CA。
检测方法很简单,把证书链按“服务器证书、中间证书、根证书”逐级导出,分别执行:
bash复制openssl x509 -in intermediate.crt -text -noout | grep "Signature Algorithm"
openssl x509 -in root.crt -text -noout | grep "Signature Algorithm"
一旦发现有 sha1WithRSAEncryption,就要么重新申请整条新链,要么用新 CA 重新签发。云厂商证书通常不会让你自己上传根证书,只要保证服务器端下发的完整证书链不包含 SHA-1 中间证书就行。但自建 CA 没有捷径,重建 CA 意味着所有下级证书都得重签,这是一个工作量不小的工程,务必提前规划好运维窗口。
4.2 内网自建 CA 怎么做才能一劳永逸
自建 CA 的核心原则是:根 CA 不直接签发服务器证书,而是先签发一个中间 CA,再用中间 CA 去签发服务器证书。根 CA 密钥离线保存,中间 CA 可在线操作。这样根 CA 密钥泄露风险小,日常签发也更灵活。
生成根 CA 时显式指定 SHA-256:
bash复制openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 \
-keyout ca.key -out ca.crt \
-subj "/C=CN/O=Internal CA/CN=Internal Root CA"
然后在 OpenSSL 配置文件的 [req] 和 [ca] 段里把 default_md 改成 sha256。这一步很关键,因为如果配置文件里没有指定摘要算法,OpenSSL 可能默认回退到 SHA-1,特别是在老版本系统上。创建中间 CA 时同样要指定:
bash复制openssl req -new -newkey rsa:2048 -sha256 \
-keyout intermediate.key -out intermediate.csr \
-subj "/C=CN/O=Internal CA/CN=Internal Intermediate CA"
中间 CA 的 CSR 交给根 CA 签名时也要显式加 -sha256:
bash复制openssl x509 -req -in intermediate.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out intermediate.crt -days 3650 -sha256
完成之后,把 intermediate.crt 与 ca.crt 合并成客户端要安装的信任链文件。每台客户端机器都要信任新的根 CA,同时移除对旧根 CA 的信任。这一步如果不做彻底,客户端会提示“证书不受信任”,和弱签名算法报错是两回事,但排障时容易混淆。
4.3 浏览器缓存和企业策略导致的“假报错”
有少数情况是证书本身已经没问题了,但用户浏览器还在报旧错误。Chrome 对证书错误结果有一定缓存时间,尤其是通过 HTTPS 访问失败后的不良状态可能被记录。遇到这种情况,清一下浏览器缓存或者无痕窗口验证即可。在企业内网环境中,还可能出现组策略强制信任了某个旧 CA 的情况,导致新证书链和本地信任根冲突。
Windows 系统还容易踩到一个坑:系统证书库中的旧根证书不会自动删除。Chrome 在 Windows 上会使用系统证书库,如果旧根 CA 还在“受信任的根证书颁发机构”列表里,而它本身是 SHA-1 签名,就可能触发“弱签名算法”的判定。解决办法是把旧根 CA 从“受信任的根证书颁发机构”移到“不信任的证书”列表,然后重新导入新根 CA。
4.4 其他容易被误判为“弱算法”的问题
NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM 报错比较专一,但浏览器在证书问题上的提示有时会混合出现。比如证书链不完整、缺少中间证书时,Chrome 可能提示 NET::ERR_CERT_AUTHORITY_INVALID;证书过期提示 NET::ERR_CERT_DATE_INVALID;域名不匹配提示 NET::ERR_CERT_COMMON_NAME_INVALID。这些错误跟弱签名算法有明确区分,不要笼统一锅端。
排查到底是不是签名算法问题,最靠谱的手段还是回到命令本身:
bash复制openssl s_client -connect yourdomain.com:443 -servername yourdomain.com 2>/dev/null | openssl x509 -noout -text | grep -E "Signature Algorithm|Public Key Algorithm|Not After"
另外,某些古老的客户端设备只支持 SHA-1 证书,比如老旧的安卓系统、老款智能电视、旧版工控机,要兼容这些设备就得保留 SHA-1 证书——但这意味着主流浏览器会直接拒绝访问。这种矛盾没有完美解法,实际中一般会单独开一个旧域名或者维护一个低版本客户端专用的入口,主站点必须坚持 SHA-256。这个取舍要提前和业务方说清楚。另外还要注意,如果服务器同时开放了 HTTP/2,Chrome 还会要求证书链完整,否则即使签名算法正确也可能报 ERR_HTTP2_PROTOCOL_ERROR,这类问题看起来和算法无关,根源还是证书链配置不到位。
5. 搞定报错之后,给你几条提高“安全水位”的建议
真正修完这个错误,我通常还会建议顺手做几件事。首先是给证书建立生命周期台账,至少记录域名、签发时间、到期时间、所用 CA、证书链文件位置、自动续期命令等,某一天证书到期不再是“突然被人发现”,而是提前就有告警。其次是给 HTTPS 打开 HSTS,让浏览器强制走 HTTPS,别让用户因为一次 HTTP 请求降级到明文。再次是关注 CA/Browser Forum 的基线要求,SHA-256 只是当前基线,证书有效期 398 天、密钥长度 2048 位以上这些约束也会随政策收紧而更新,保持对新规的了解能避免下一次“突然报错”。
我个人在实际操作中的体会是,这类“浏览器突然不认旧证书”的问题,根因大多是历史上用了系统默认值。OpenSSL 生成自签名证书时如果不显式写 -sha256,在老版本里默认就是 SHA-1;越老的教程越容易留下这个坑。所以现在不论生成什么证书,我都会在命令里显式把摘要算法写出来,不依赖默认值。养成这个习惯之后,不仅是 NET::ERR_CERT_WEAK_SIGNATURE_ALGORITHM,很多证书相关的隐蔽问题都能在产生之前被掐掉。最后再分享一个小技巧:换证书前先把旧的证书链用 openssl crl2pkcs7 -nocrl -certfile old_chain.pem | openssl pkcs7 -print_certs 导出来留档,一旦新证书有问题可以秒级回滚,不用在排障的时候手忙脚乱找备份。
