1. 当浏览器说"不信任"时发生了什么?
上周我在部署内部测试环境时,遇到了一个熟悉的红色警告页。Chrome用刺眼的红色告诉我:"您的连接不是私密连接",错误代码ERR_CERT_COMMON_NAME_INVALID。这就像你去银行办事,工作人员核对身份证时发现名字和系统记录不符——浏览器就是那个较真的柜员,SSL证书就是你的数字身份证。
这个错误的核心在于证书主题标识(Subject Alternative Name/SAN)和实际访问域名的匹配问题。想象你拿着写有"张三"的身份证去办理"张小三"的业务,虽然只差一个字,但系统就会拒绝。浏览器验证证书时也会做类似的严格比对:
- 首先检查证书的Common Name(CN)字段
- 然后检查SAN扩展中的DNS记录
- 最后比对地址栏域名与上述字段是否完全一致
我遇到过最典型的场景是:为api.example.com生成的证书,却被用在www.example.com上。虽然同属一个主域,但对浏览器来说就像用A小区的门禁卡刷B小区的闸机——根本行不通。
2. 诊断证书问题的三板斧
2.1 用OpenSSL做"体检报告"
当错误出现时,第一步应该是检查证书的详细内容。就像医生用听诊器检查病人,我们可以用OpenSSL命令查看证书"内脏":
openssl x509 -in server.crt -text -noout重点关注两个部分:
- Subject:这里会显示CN字段
- X509v3 Subject Alternative Name:这里会列出所有有效域名
我常用这个组合命令快速提取关键信息:
openssl x509 -in server.crt -noout -text | grep -E "Subject:|DNS:"2.2 浏览器开发者工具的妙用
现代浏览器的开发者工具是排查SSL问题的瑞士军刀。在Chrome中:
- 点击警告页面的"高级"→"继续前往"
- 按F12打开开发者工具
- 转到Security标签页
这里能看到完整的证书链,点击"View certificate"可以直观看到证书包含的所有域名。我经常发现开发者配置了多域名证书,但漏掉了关键的二级域名。
2.3 证书链完整性检查
有时候问题不在终端证书,而在证书链缺失。用这个命令验证:
openssl verify -CAfile root_ca.crt -untrusted intermediate.crt server.crt曾经有个生产环境故障,就是因为运维同学忘记部署中间证书,导致所有iOS设备报错,而Android却正常——这种平台差异性问题特别容易踩坑。
3. 正确生成证书的实战指南
3.1 自签名证书的正确姿势
测试环境常用自签名证书,但很多人直接用简单命令生成:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out cert.pem这样生成的证书只有CN字段,没有SAN扩展,现代浏览器会直接拒绝。正确的做法是使用配置文件:
# ssl.conf [req] distinguished_name = req_distinguished_name x509_extensions = v3_req prompt = no [req_distinguished_name] CN = test.example.com [v3_req] keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = test.example.com DNS.2 = *.test.example.com然后执行:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -config ssl.conf3.2 多域名与通配符证书配置
对于需要支持多个域名的情况,SAN扩展是必须的。在配置文件的[alt_names]段这样配置:
DNS.1 = example.com DNS.2 = www.example.com DNS.3 = api.example.com DNS.4 = *.cdn.example.com注意通配符()只能匹配同一级子域名,比如.example.com可以匹配a.example.com,但不能匹配a.b.example.com。
3.3 CSR生成的最佳实践
申请商业证书时,CSR(证书签名请求)的生成质量直接影响最终证书的可用性。我推荐这个流程:
- 生成私钥:
openssl genpkey -algorithm RSA -out private.key -aes256- 创建CSR配置文件:
[req] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn req_extensions = req_ext [dn] CN = www.example.com O = "Example Company" L = Shanghai C = CN [req_ext] subjectAltName = @alt_names [alt_names] DNS.1 = www.example.com DNS.2 = example.com IP.1 = 192.168.1.1- 生成CSR:
openssl req -new -key private.key -out request.csr -config csr.conf4. 常见场景解决方案
4.1 本地开发环境配置
开发时经常需要localhost或127.0.0.1的HTTPS支持。这是我最常用的本地证书配置:
[alt_names] DNS.1 = localhost IP.1 = 127.0.0.1 IP.2 = ::1对于前端开发,还需要配置webpack或vite:
// vite.config.js import { defineConfig } from 'vite' import fs from 'fs' export default defineConfig({ server: { https: { key: fs.readFileSync('localhost-key.pem'), cert: fs.readFileSync('localhost.pem') } } })4.2 容器化环境处理
在Docker环境中,证书管理需要特别注意:
COPY ./certs /etc/ssl/certs RUN update-ca-certificatesKubernetes中可以通过Secret挂载证书:
apiVersion: v1 kind: Secret metadata: name: tls-secret type: kubernetes.io/tls data: tls.crt: base64编码的证书 tls.key: base64编码的私钥4.3 证书自动续期方案
对于Let's Encrypt证书,我推荐使用certbot的docker版本:
docker run -it --rm --name certbot \ -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ certbot/certbot renew设置cron任务自动续期:
0 0 * * * docker run --rm -v /etc/letsencrypt:/etc/letsencrypt -v /var/lib/letsencrypt:/var/lib/letsencrypt certbot/certbot renew --quiet5. 高级排查技巧
5.1 证书透明度日志检查
当证书看起来一切正常但依然报错时,可以检查证书透明度日志:
openssl x509 -in cert.pem -noout -text | grep -A1 "CT Precertificate SCTs"或者使用在线工具检查证书是否被意外吊销。
5.2 OCSP装订配置
OCSP装订可以显著提高SSL握手性能。在Nginx中配置:
ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /path/to/full_chain.pem;验证装订是否生效:
openssl s_client -connect example.com:443 -status -servername example.com5.3 HSTS策略优化
对于生产环境,建议启用HSTS:
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";但要注意这会导致测试环境无法降级到HTTP,开发时可以先注释掉这行。