macOS 浏览器拒绝内网 HTTPS,curl 却完全正常——397 天叶证书硬限制
搭一套内网 HTTPS:自建根 CA、安装到 macOS 系统钥匙串、配好 /etc/hosts,看似万事俱备。打开 Safari——「无法与服务器建立安全连接」。换 Chrome——ERR_CONNECTION_CLOSED。重启、重装证书、换浏览器,统统无效。
最诡异的地方在于,命令行工具一个都没报错:
curl --cacert rootCA.crt https://internal.dev/ # 200 OK
openssl s_client -CAfile rootCA.crt \
-connect internal.dev:443 -servername internal.dev
# Verify return code: 0 (ok)
security find-certificate -c "My Root CA" \
/Library/Keychains/System.keychain
# (证书存在)链路合法、CA 信任、SAN 齐全——但浏览器就是不认。
现象
- Safari:「Safari 浏览器无法打开页面 · 无法与服务器建立安全连接」
- Chrome / Edge / Arc(macOS):
ERR_CONNECTION_CLOSED - curl / wget / openssl s_client:全部正常
- Linux 上的 Chrome / curl:正常
- 证书包含正确的 v3 扩展(BasicConstraints CA:FALSE、EKU serverAuth、SAN DNS 条目)
根因
Apple 在 2020 年 9 月发布技术文档 HT211025,规则生效日期 2020-09-01:
TLS 服务器叶证书的有效期(
notAfter - notBefore)不得超过 398 天。违反规则的证书在 Apple 操作系统上不被信任。
实际上,398 是宣传数字,脚本里安全上限是 397 天(留 1 天缓冲)。
这条规则影响范围:
- macOS 11 Big Sur 及以上
- iOS 14 及以上
- Safari 14 及以上
- macOS 上的 Chrome / Edge / Arc / Brave(它们在 macOS 上走
CertVerifyProcMac,底层调用 Apple 的评估器,同样受约束) - Tauri / Electron 内嵌 WKWebView 的 macOS 应用
不受影响的:
- curl / wget / openssl s_client(自带 BoringSSL / OpenSSL,不走 NSURLSession)
- Linux 上的任何浏览器
- 服务端 TLS 验证(Node.js、Python、Go 等)
这就是为什么命令行全绿、浏览器全红——问题不在证书本身,在 Apple 的评估器。
绝大多数 2020 年前的教程默认用 -days 3650(10 年)或 -days 730(2 年),都超过 397 天。直接复制这些命令,在 macOS 上会被浏览器无声地拒绝,报错信息完全看不出是「有效期太长」。
正解
只需重新签发叶证书,有效期改为 397 天以内。根 CA 完全不需要动(根 CA 可以保持 10 年甚至更长)。
# 复用现有根 CA,只重签叶证书
openssl x509 -req -in server.csr \
-CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out server.crt -days 397 -sha256 \
-extensions v3_req -extfile cert.cnf
# 重建 fullchain
cat server.crt rootCA.crt > fullchain.pem
# 热重载 nginx(不需要重启服务)
nginx -s reload如果维护的是 gen-ca.sh 类脚本,把变量改掉:
# 改之前
LEAF_DAYS=$((365 * 2)) # ❌ 730 天,macOS 浏览器拒绝
# 改之后
LEAF_DAYS=397 # ✅ 恰好在 Apple 上限以内重签叶证书后,Safari 和 Chrome 均无需重启,刷新页面即可恢复绿锁。根 CA 有效期不受此规则约束,无需重新安装信任。
运维后果:每年续签
397 天约等于 13 个月。上线后需要设日历提醒,在到期前 30 天重新执行签发脚本并热重载 TLS 终止器。如果想彻底自动化,可以把内网 CA 换成支持 ACME 协议的方案(如 step-ca),Caddy 等反向代理可以对接 ACME 自动续期,从此不再手动操心 397 天的窗口。
openssl x509 -in fullchain.pem -noout -dates
# notBefore=Jun 1 00:00:00 2025 GMT
# notAfter =Jun 2 00:00:00 2027 GMT ← 超过 397 天,必挂浏览器的报错(「无法建立安全连接」/ ERR_CONNECTION_CLOSED)没有任何提示说明是有效期问题,必须手动算 notAfter - notBefore。
一句话外卖
macOS 上 curl 通而浏览器挂,先查叶证书有效期是否超过 397 天——Apple 的评估器悄悄执行了命令行工具从不理会的这条规则。