自签名SSL证书在浏览器里总是不受信任,是因为它没有接入浏览器内置的信任链:浏览器只信任系统或浏览器自带的根证书库(Root Store)里预置的 CA,而自签名证书的签发者和持有者是同一个实体,无法回溯到任何受信根,因此在链验证环节会直接被判失败。想让自签名证书“变绿”,正确做法是自己做一个本地根 CA 并把它装进信任库,而不是签一张证书就完事。
浏览器判断“受信任”的依据是什么?
结论:浏览器做的是证书链验证(Chain of Trust),不是看证书内容像不像真的。
验证逻辑是:服务器下发的叶子证书(站点证书)→ 中间 CA 证书 → 根 CA 证书,这条链必须最终停在浏览器信任库中的某个根证书上,且每一级签名都能用上一级公钥验签通过、各项扩展字段合规。
公开 CA 的根证书是操作系统或浏览器厂商出厂预置的,比如 Windows 的“受信任的根证书颁发机构”存储区、macOS 的“系统根证书”钥匙串、Firefox 自带的 cert9.db。你自己用 openssl req -x509 生成的那张证书,签发者字段(Issuer)等于主体字段(Subject),链在这里就断了,浏览器只能报错。
常见报错对照:Chrome / Edge 显示 NET::ERR_CERT_AUTHORITY_INVALID,Firefox 显示 SEC_ERROR_UNKNOWN_ISSUER,curl 显示 unable to get local issuer certificate。
除了“不在信任库”,还有哪些坑会让自签名证书失败?
结论:即使手工把证书加进信任库,仍有一半的自签名证书会因为扩展字段不合规被拒。
按出现频率排序:
- 缺 SAN 扩展:Chrome 58 起完全忽略 CN 字段,只认 subjectAltName。用老教程的
-subj "/CN=example.local" 生成,必然报 ERR_CERT_COMMON_NAME_INVALID。
- 有效期太长:CA/Browser Forum 要求 TLS 证书有效期不超过 398 天,Chrome 已按此执行。很多人图省事写
-days 3650,直接被拒。
- 签名算法过弱:SHA-1 签名自 2017 年起被主流浏览器全面拒绝,必须用 SHA-256。
- 基础约束缺失:作为 CA 的根证书必须带
basicConstraints=critical,CA:TRUE,否则下级证书无法通过链验证。
- 缺 EKU:服务器证书需要
extendedKeyUsage=serverAuth,否则部分客户端不认。
- 证书链不完整:Nginx 的
ssl_certificate 里只放了叶子证书、没拼中间证书,会报 unable to verify。
- 系统时间偏差:客户端时钟错到证书生效期之外,报
ERR_CERT_DATE_INVALID。
自签名证书怎么做才能被浏览器信任?
结论:不要直接自签服务器证书,而是先建一个本地根 CA,签出服务器证书,再把根证书装进客户端信任库。
命令行示例(openssl 3.x):
openssl req -x509 -newkey rsa:4096 -nodes -days 3650 \
-keyout rootCA.key -out rootCA.crt \
-subj "/CN=My Local Root CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
# 2. 生成服务器私钥与 CSR
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
-subj "/CN=dev.local"
# 3. 用根 CA 签发服务器证书(90 天 + SAN + serverAuth)
openssl x509 -req -in server.csr -CA rootCA.crt -CAkey rootCA.key \
-CAcreateserial -out server.crt -days 90 -sha256 \
-extfile <(printf "subjectAltName=DNS:dev.local,DNS:*.dev.local,IP:127.0.0.1\nbasicConstraints=CA:FALSE\nextendedKeyUsage=serverAuth")
导入信任库:Windows 用 certmgr.msc → 受信任的根证书颁发机构;macOS 双击后设为“始终信任”;Debian/Ubuntu 放到 /usr/local/share/ca-certificates/ 再执行 update-ca-certificates。
注意点:Firefox 使用独立的信任库,系统导入不管用,需在 about:preferences#privacy → 查看证书 → 证书颁发机构里单独导入。另外根 CA 私钥务必离线保存,泄露等于给攻击者签任意域名的能力。
有没有更省事的替代方案?
结论:本机开发或内网小规模场景,直接用 mkcert 比自己写openssl命令稳。
mkcert 会自动生成本地根 CA、自动注入各平台信任库(含 Firefox),并自动签出带正确 SAN 的服务器证书:
mkcert -install
mkcert dev.local 127.0.0.1 ::1
能上公网的域名,直接上 Let's Encrypt,用 DNS-01 验证还能给纯内网域名签证书,certbot 配合 --preferred-challenges dns 即可。
最后提醒一点:如果站点提交过 HSTS(Strict-Transport-Security),Chrome 会禁止用户点“继续前往”绕过证书错误,自签名证书连临时跳过都做不到。