关键是要构建完整信任链:先将自签名根ca证书导入“系统”钥匙串并单独设ssl为“始终信任”,再用其签发含san的终端证书,最后配置服务使用证书链(server-chain.crt+server.key)并确保hosts映射正确。
在 macos 上让本地开发服务(如 localhost、myapp.test 或 127.0.0.1)通过 https 安全运行,关键不是“生成证书”就结束,而是构建一条浏览器和系统都认可的完整信任链——从根证书到终端证书,每一步都需精准配置。
生成并永久信任自签名根 CA 证书
所有后续证书都由它签发,只需信任一次,长期有效:
- 用 OpenSSL 生成 4096 位私钥和自签名根证书(建议有效期 10 年):
openssl genrsa -out root-ca.key 4096openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 3650 -out root-ca.crt -subj "/C=CN/ST=Beijing/L=Beijing/O=LocalDev/CN=Local Development Root CA" - 双击
root-ca.crt→ 导入「系统」钥匙串(不是「登录」)→ 找到该证书 → 右键「显示简介」→ 展开「信任」→ 将「SSL」下拉菜单设为「始终信任」→ 关闭并输入密码确认 - 注意:仅勾选顶部“始终信任”无效,必须单独设置 SSL 用途为“始终信任”,否则 Safari、Chrome 和 iOS 模拟器仍会报错
签发含完整 SAN 的终端证书
现代浏览器拒绝仅靠 Common Name 匹配的证书。必须通过配置文件明确声明所有可访问域名与 IP:
- 新建
cert.conf,包含[alt_names]段落,例如:DNS.1 = localhostDNS.2 = *.testDNS.3 = *.localIP.1 = 127.0.0.1IP.2 = ::1 - 生成终端私钥和 CSR:
openssl req -new -keyout server.key -out server.csr -newkey rsa:4096 -nodes -sha256 -config cert.conf - 用根证书签发最终证书:
openssl x509 -req -in server.csr -CA root-ca.crt -CAkey root-ca.key -CAcreateserial -out server.crt -days 365 -sha256 - 验证证书是否正确继承根证书签名:
openssl verify -CAfile root-ca.crt server.crt(应返回server.crt: OK)
在服务中正确加载证书链
只提供终端证书(server.crt)不够,某些客户端(尤其 iOS 或 PWA)要求完整链。需合并根证书到终端证书文件:
- 执行:
cat server.crt root-ca.crt > server-chain.crt - 将
server-chain.crt和server.key配置到你的服务中:
• Webpack Dev Server:在devServer.https中指定cert和key
• Express:使用https.createServer({ key, cert: server-chain.crt }, app)
• Nginx:设置ssl_certificate指向server-chain.crt,ssl_certificate_key指向server.key - 确保
/etc/hosts包含对应映射,例如:127.0.0.1 myapp.test
验证与常见问题排查
访问 https://localhost 或 https://myapp.test 仍报错?检查以下几点:
- 是否在「钥匙串访问」的「系统」钥匙串中看到根证书?「登录」钥匙串中的证书不被系统网络栈信任
- 根证书的「SSL」信任设置是否为「始终信任」?这是最常被忽略的关键点
- 终端证书是否由该根证书签发?用
openssl x509 -in server.crt -text -noout | grep "Issuer:"查看 Issuer 是否匹配根证书的 Subject - Chrome 浏览器是否已重启?部分版本缓存证书信任状态,仅刷新页面无效
- iOS 设备调试时,需手动安装
root-ca.crt并在「设置 → 已下载描述文件」中启用完全信任











