OpenSSL生成带SAN扩展的自签名证书,解决HTTPS开发环境证书错误

📅 发布时间:2026/8/7 10:10:38
OpenSSL生成带SAN扩展的自签名证书,解决HTTPS开发环境证书错误
1. 项目概述从一次恼人的浏览器报错说起如果你正在搭建一个本地开发环境或者在内网部署一个Web服务兴致勃勃地在浏览器里输入https://localhost:8443或https://myapp.local结果迎面而来的不是期待中的页面而是一个鲜红的警告页上面写着“您的连接不是私密连接”错误代码是NET::ERR_CERT_COMMON_NAME_INVALID那你一定感同身受。这个场景太常见了尤其是在使用自签名证书进行HTTPS开发测试时。几年前我们可能只需要在证书的“通用名称”Common Name, CN字段里填上域名浏览器就能勉强接受。但时代变了以Chrome为代表的现代浏览器为了提升安全性早已收紧了策略。这个错误的根源十有八九是你的SSL/TLS证书里缺少了一个关键的东西主题备用名称Subject Alternative Name, SAN扩展。简单来说浏览器现在不仅要看证书是发给谁的CN还要看证书“还能”用于哪些其他名称SAN。如果你的访问地址比如IP地址、或者一个自定义的本地域名没有明确列在SAN列表里即使CN匹配浏览器也会无情地抛出这个错误。网上有很多教程教你“忽略警告”、“继续访问”但这只是掩耳盗铃治标不治本。更糟糕的是在一些严格的开发场景比如需要调用浏览器严格模式的API如Service Worker、WebRTC等或自动化测试中你根本无法绕过这个安全警告。所以唯一的正道就是生成一个“合规”的、带有正确SAN扩展的自签名证书。而完成这个任务最经典、最通用的工具就是OpenSSL。在这篇分享里我将以一个多年运维和开发者的视角带你彻底弄懂SAN扩展是什么为什么它现在如此重要并手把手教你用OpenSSL从零开始生成一个完美的、带SAN扩展的证书一劳永逸地解决NET::ERR_CERT_COMMON_NAME_INVALID报错。无论你是前端开发、后端开发还是运维工程师这套方法都将是你的必备技能。2. 核心原理为什么CN不再够用SAN成为必选项要解决问题先得理解问题。我们得从SSL/TLS证书的基本结构说起。2.1 通用名称CN的时代局限性在早期的X.509证书标准中通用名称Common Name字段是标识证书持有者身份的核心。当你访问https://www.example.com时浏览器会检查服务器返回的证书看其CN字段是否与访问的域名匹配。如果匹配就认为证书是有效的。但这种设计存在明显的局限性一个证书只能绑定一个域名CN字段通常只有一个值。如果你有一个证书CN是www.example.com那么访问example.com无www或者api.example.com时浏览器就会报错因为名称不匹配。无法支持IP地址CN字段设计上是用于域名DNS名称的。虽然技术上可以把IP地址填进去但这不符合规范很多客户端软件包括新版本浏览器对此支持不佳或直接拒绝。安全策略模糊CN的用途过于宽泛除了标识服务器历史上还用于标识电子邮件地址或个人姓名这导致安全策略不够清晰和严格。2.2 主题备用名称SAN的登场与优势为了解决CN的不足主题备用名称Subject Alternative Name扩展被引入到X.509 v3标准中。SAN扩展允许一个证书关联多个身份标识。你可以把它想象成证书的“别名”或“附加许可列表”。一个SAN扩展里可以包含多种类型的名称DNS名称例如example.com,www.example.com,*.example.com通配符。IP地址例如192.168.1.1,::1IPv6本地回环。电子邮件地址userexample.com。统一资源标识符URI等其他类型。现代浏览器Chrome 58其他主流浏览器也已跟进的安全策略已经明确他们将主要甚至完全依赖SAN扩展来验证服务器身份CN字段基本被忽略。这就是为什么你只配置了CN浏览器会报ERR_CERT_COMMON_NAME_INVALID的根本原因——它没在SAN里找到你正在访问的那个名字。注意根据CA/浏览器论坛CA/Browser Forum制定的基线要求Baseline Requirements自2015年起所有公开信任的证书颁发机构CA签发的SSL证书都必须包含SAN扩展。这已经成为了行业强制标准。2.3 自签名证书与SAN对于公开的网站我们会从Let‘s Encrypt、DigiCert等CA购买或申请免费证书这些CA签发的证书天然就包含了正确的SAN设置。问题主要出在自签名证书Self-Signed Certificate上。自签名证书是我们自己充当CA为自己颁发的证书。它不受公共信任链认可所以浏览器会提示“不安全”但在开发、测试、内网环境中极其有用。我们在用OpenSSL生成自签名证书时必须手动地、正确地配置SAN扩展才能满足现代浏览器的验证要求。3. 工具准备与核心概念澄清工欲善其事必先利其器。在开始动手前我们需要准备好环境并理清几个关键概念。3.1 OpenSSL的安装与验证OpenSSL是一个强大的开源密码学工具包几乎在所有Linux发行版和macOS上都预装了。Windows用户可以从其官网或通过包管理器如Chocolatey、Scoop安装。首先打开终端Linux/macOS或命令提示符/PowerShellWindows检查你的OpenSSL版本openssl version确保你使用的是相对较新的版本如1.1.1或3.x老版本如0.9.x的命令和功能可能有所不同。3.2 密钥、证书请求CSR与证书用OpenSSL生成证书通常涉及三个核心文件理解它们的关系至关重要私钥Private Key 通常为.key文件是什么一长串高度机密的随机数据是加密体系的基石。必须绝对保密作用用于对信息进行解密对接收到的密文或签名对要发送的数据。生成命令openssl genrsa或openssl ecparam。证书签名请求Certificate Signing Request, CSR 通常为.csr文件是什么一个包含了你的公钥、组织信息以及最重要的——你想要证书包含的域名/IP列表SAN的申请文件。作用将它提交给CA无论是公共CA还是你自己的私有CA请求其为你颁发证书。CSR中包含了SAN扩展的配置信息。生成命令openssl req -new。证书Certificate 通常为.crt或.pem文件是什么由CA或你自己使用其私钥对CSR进行签名后生成的文件。里面包含了你的公钥、身份信息、SAN列表以及CA的签名。作用服务器将其提供给客户端浏览器客户端用CA的公钥验证签名并检查其中的身份信息SAN是否与访问的地址匹配。生成命令自签名openssl x509 -req或openssl req -x509。核心流程先生成私钥然后用私钥生成包含SAN信息的CSR最后用私钥自签名或CA的私钥对CSR进行签名得到最终的证书。3.3 配置文件openssl.cnf的角色OpenSSL很多操作依赖于一个配置文件通常名为openssl.cnf。它定义了证书的默认参数、扩展项包括v3扩展如SAN的格式。在生成CSR和证书时我们需要通过这个配置文件或命令行参数来告诉OpenSSL“请把SAN扩展信息加进去”。系统通常有一个全局的openssl.cnf。为了灵活性我强烈建议在项目目录下创建一个专用的配置文件副本进行修改这样不会影响系统其他用途。4. 手把手实操生成带SAN扩展的自签名证书理论讲完进入实战环节。我将演示一个最经典的场景为本地开发生成一个同时支持localhost、127.0.0.1和myapp.local的证书。4.1 第一步准备SAN配置文件这是最关键的一步。在你的项目目录下创建一个名为san.cnf的文件。内容如下[req] default_bits 2048 distinguished_name req_distinguished_name req_extensions v3_req prompt no [req_distinguished_name] countryName CN stateOrProvinceName Some-State localityName Some-City organizationName My Company Ltd organizationalUnitName IT Department commonName myapp.local # 这里填一个主要的域名但浏览器主要看SAN [v3_req] basicConstraints CA:FALSE keyUsage nonRepudiation, digitalSignature, keyEncipherment subjectAltName alt_names # 关键这里指向了下面的alt_names节 [alt_names] # SAN列表定义在这里 DNS.1 localhost DNS.2 myapp.local IP.1 127.0.0.1 # 你可以继续添加 # DNS.3 api.myapp.local # IP.2 192.168.1.100参数解析与注意事项[req]定义了证书请求的默认参数。req_extensions v3_req这行至关重要它启用了v3扩展。commonName虽然浏览器不主要看它但一些老式客户端或日志系统可能还会用到建议填写一个有意义的主域名。[v3_req]定义了v3扩展的内容。subjectAltName alt_names是灵魂它通过符号引用了下面[alt_names]节的配置。[alt_names]这里就是SAN列表。DNS.1,DNS.2表示DNS名称IP.1表示IP地址。数字索引从1开始递增。请务必根据你的实际需要修改这里的值。实操心得对于本地开发我习惯把localhost、127.0.0.1、::1(IPv6) 以及我自定义的测试域名如myapp.test都加进去这样无论用什么方式访问证书都能匹配。如果你需要在内网用IP访问也一定要把IP地址加进来。4.2 第二步生成私钥和证书签名请求CSR使用上一步创建的配置文件来生成CSR。这个命令会同时生成私钥。openssl req -new -nodes -newkey rsa:2048 \ -keyout server.key \ # 输出私钥文件 -out server.csr \ # 输出CSR文件 -config san.cnf # 使用我们的配置文件命令拆解req -new创建一个新的证书请求。-nodes代表“no DES”即不对生成的私钥进行加密。对于开发测试环境这样更方便服务器启动时不需要输入密码。生产环境请务必移除这个参数并为私钥设置强密码。-newkey rsa:2048指定生成一个2048位长度的RSA密钥对。-keyout server.key指定私钥输出文件名。-out server.csr指定CSR输出文件名。-config san.cnf指定使用我们自定义的配置文件这样才能将SAN信息写入CSR。执行后当前目录下会生成server.key私钥和server.csr证书请求两个文件。验证CSR中的SAN信息 生成后强烈建议检查一下CSR确认SAN信息是否正确包含openssl req -in server.csr -noout -text在输出的文本中你应该能找到X509v3 Subject Alternative Name:这一节下面列出的正是你在san.cnf中配置的localhost、myapp.local和127.0.0.1。4.3 第三步生成自签名证书现在我们用这个CSR和对应的私钥自己给自己“签名”生成最终的证书。openssl x509 -req -sha256 -days 365 \ -in server.csr \ # 输入CSR文件 -signkey server.key \ # 用于签名的私钥就是刚才生成的 -out server.crt \ # 输出证书文件 -extfile san.cnf \ # 关键再次指定配置文件 -extensions v3_req # 关键指定使用配置文件中的v3_req扩展节命令拆解x509 -req处理一个证书请求CSR来生成证书。-sha256指定使用SHA-256哈希算法进行签名。这是目前的安全标准避免使用已不安全的SHA-1或MD5。-days 365证书的有效期这里是365天。可以根据需要调整。-signkey server.key指定用于签名的私钥。因为是自签名所以用我们自己的私钥。-extfile san.cnf -extensions v3_req这是整个命令的灵魂你必须通过这两个参数告诉openssl x509命令在生成证书时要从san.cnf配置文件的[v3_req]节中读取扩展信息包括SAN并将其写入最终的证书文件。如果漏了这一步生成的证书将不包含SAN扩展前功尽弃。执行成功后你会得到server.crt文件。4.4 第四步验证生成的证书最后让我们检查一下劳动成果确保证书包含了我们想要的SAN。openssl x509 -in server.crt -noout -text仔细查看输出找到X509v3 extensions:部分你应该能看到类似下面的内容X509v3 Subject Alternative Name: DNS:localhost, DNS:myapp.local, IP Address:127.0.0.1如果看到了这个并且列表正确那么恭喜你一个带有完整SAN扩展的自签名证书已经成功生成5. 在服务器中配置并使用证书证书生成好了接下来就是把它用起来。这里以几个常见的Web服务器为例。5.1 Nginx 配置示例在Nginx的server配置块中指定证书和私钥的路径server { listen 443 ssl http2; server_name localhost myapp.local; ssl_certificate /path/to/your/server.crt; ssl_certificate_key /path/to/your/server.key; # 可选提高SSL安全性的一些配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # ... 其他配置 }配置完成后运行nginx -t测试配置然后nginx -s reload重载配置。5.2 Apache HTTP Server 配置示例在Apache的VirtualHost配置中启用SSL并指定证书VirtualHost *:443 ServerName myapp.local ServerAlias localhost SSLEngine on SSLCertificateFile /path/to/your/server.crt SSLCertificateKeyFile /path/to/your/server.key # ... 其他配置 /VirtualHost使用apachectl configtest测试然后重启Apache服务。5.3 Node.js (Express) 示例在Node.js的HTTPS服务器中直接使用文件const https require(https); const fs require(fs); const express require(express); const app express(); const options { key: fs.readFileSync(/path/to/your/server.key), cert: fs.readFileSync(/path/to/your/server.crt) }; https.createServer(options, app).listen(8443, () { console.log(HTTPS server running on port 8443); });5.4 本地DNS解析针对自定义域名如果你在SAN里配置了像myapp.local这样的自定义域名你还需要在本地的hosts文件中将其解析到127.0.0.1。Windows编辑C:\Windows\System32\drivers\etc\hostsLinux/macOS编辑/etc/hosts在文件末尾添加一行127.0.0.1 myapp.local保存后你就可以在浏览器中用https://myapp.local:8443访问你的服务了。6. 在浏览器中信任自签名证书可选但推荐完成以上步骤后你的Chrome浏览器应该不再显示NET::ERR_CERT_COMMON_NAME_INVALID错误但可能仍会显示“您的连接不是私密连接”这是因为你的自签名证书不被操作系统或浏览器的信任根证书列表所信任。对于开发环境我们可以手动信任它。重要警告此操作仅适用于你完全信任的、自己生成的开发/测试证书。切勿将不明来源的证书加入信任列表6.1 将证书导入操作系统信任库这是最彻底的方法信任后所有浏览器和系统应用都会认可该证书。Windows:双击server.crt文件。点击“安装证书”。选择“当前用户”或“本地计算机”需要管理员权限。选择“将所有的证书都放入下列存储”点击“浏览”。选择“受信任的根证书颁发机构”点击确定并完成。macOS:双击server.crt文件这会打开“钥匙串访问”应用。在“钥匙串”列表中选择“系统”或“登录”系统范围需要密码。找到你刚导入的证书通常以你配置的commonName命名。双击该证书展开“信任”部分。将“使用此证书时”设置为“始终信任”然后关闭窗口并输入密码确认。Linux (Ubuntu/Debian):sudo cp server.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates6.2 Chrome浏览器的单独信任如果你不想影响系统可以只在Chrome中信任。在Chrome中访问chrome://settings/security点击“管理证书”。在“受信任的根证书颁发机构”标签页中点击“导入”然后选择你的server.crt文件并按照向导完成。完全关闭并重启Chrome。完成上述任一信任操作后再次访问你的HTTPS站点那个恼人的红色警告页面就应该消失了取而代之的是一个正常的、带锁的HTTPS连接标识可能显示为“安全”或“证书有效”。7. 进阶技巧与常见问题排查掌握了基础操作后我们再来探讨一些进阶场景和可能遇到的坑。7.1 一键生成脚本将上述步骤整合成一个Shell脚本generate_cert.sh方便重复使用#!/bin/bash DOMAINSDNS:localhost,DNS:myapp.local,IP:127.0.0.1 COMMON_NAMEmyapp.local OUTPUT_DIR./certs CONFIG_FILE$OUTPUT_DIR/san.cnf KEY_FILE$OUTPUT_DIR/server.key CSR_FILE$OUTPUT_DIR/server.csr CRT_FILE$OUTPUT_DIR/server.crt mkdir -p $OUTPUT_DIR # 1. 创建配置文件 cat $CONFIG_FILE EOF [req] default_bits 2048 distinguished_name req_distinguished_name req_extensions v3_req prompt no [req_distinguished_name] C CN ST Some-State L Some-City O My Org OU IT CN $COMMON_NAME [v3_req] keyUsage digitalSignature, keyEncipherment extendedKeyUsage serverAuth subjectAltName $DOMAINS EOF # 2. 生成私钥和CSR openssl req -new -nodes -newkey rsa:2048 \ -keyout $KEY_FILE \ -out $CSR_FILE \ -config $CONFIG_FILE # 3. 生成自签名证书 openssl x509 -req -sha256 -days 365 \ -in $CSR_FILE \ -signkey $KEY_FILE \ -out $CRT_FILE \ -extfile $CONFIG_FILE \ -extensions v3_req echo 证书生成完成 echo 私钥: $KEY_FILE echo 证书: $CRT_FILE记得给脚本执行权限chmod x generate_cert.sh。7.2 生成通配符证书的SAN有时你需要支持一个域的所有子域名比如*.example.com。请注意通配符证书只能匹配同一级子域名不能跨级即*.example.com匹配a.example.com但不匹配a.b.example.com。在san.cnf的[alt_names]节中你可以这样配置[alt_names] DNS.1 example.com DNS.2 *.example.com DNS.3 localhost这样证书就能同时支持根域名、所有一级子域名以及本地环回了。7.3 常见问题排查表即使按照步骤操作有时还是会遇到问题。下表汇总了常见症状和解决方案问题现象可能原因排查步骤与解决方案浏览器仍报ERR_CERT_COMMON_NAME_INVALID1. SAN未正确写入证书。2. 访问的地址不在SAN列表中。3. 浏览器缓存了旧证书。1. 用openssl x509 -in server.crt -noout -text确认SAN列表存在且正确。2. 检查你访问的URL域名或IP是否完全一致地出现在SAN的DNS或IP条目中。3. 清除浏览器SSL状态缓存chrome://net-internals/#hsts在“Delete domain security policies”中输入域名并删除。或尝试无痕模式。浏览器提示“不是私密连接”但错误码不同如ERR_CERT_AUTHORITY_INVALID自签名证书不被信任。按照第6节的方法将server.crt导入到操作系统或浏览器的受信任根证书颁发机构。服务器启动失败提示“SSL私钥与证书不匹配”用于签名证书的私钥和当前配置中使用的私钥不是同一对。确保Nginx/Apache/Node.js配置中ssl_certificate_key或key选项指向的.key文件就是当初生成CSR和签名证书时用的那个。它们必须成对出现。SAN列表中有IP但用IP访问仍报错1. IP地址格式错误。2. 服务器监听的不是该IP。3. 本地hosts文件覆盖或DNS问题。1. 确认SAN中IP格式正确如IP.1 192.168.1.1。2. 确认Web服务器监听在0.0.0.0:443或具体的IP上。3. 尝试直接用https://127.0.0.1访问排除域名解析问题。证书生成成功但Chrome显示“证书已过期”或“尚未生效”系统时间不正确或证书有效期设置有问题。1. 检查你的操作系统时间是否准确。2. 用openssl x509 -in server.crt -noout -dates查看证书的起止日期。使用脚本生成后SAN扩展是空的在生成证书x509 -req命令中漏掉了-extfile和-extensions参数。这是最高频的错误必须确保生成证书的命令包含了-extfile san.cnf -extensions v3_req。CSR里有SAN不代表证书里一定有签名这一步必须显式指定。7.4 关于证书格式的补充你可能还会遇到.pem,.der,.pfx/.p12等格式。简单来说PEM最常见的格式文本格式以-----BEGIN XXX-----和-----END XXX-----包裹。我们生成的.crt和.key通常就是PEM格式。DER二进制格式内容与PEM相同。PFX/P12一种归档格式可以同时包含证书、私钥以及可能的中间证书通常有密码保护。常用于Windows服务器或Java应用。OpenSSL可以很方便地在这些格式间转换例如将PEM证书和私钥打包成P12openssl pkcs12 -export -out server.p12 -inkey server.key -in server.crt执行后会提示你设置一个导出密码。8. 总结与最佳实践建议走到这里你已经不仅解决了NET::ERR_CERT_COMMON_NAME_INVALID这个具体错误更掌握了OpenSSL生成合规自签名证书的核心流程。回顾整个过程最关键的两个点永远是正确配置SAN扩展以及在生成证书的最后一步通过-extfile和-extensions参数将其写入。根据我多年的经验再分享几个最佳实践为不同环境使用不同证书开发、测试、预生产、生产环境应使用不同的证书即使是自签名的。这能避免配置混淆带来的安全风险。妥善保管私钥私钥 (.key文件) 等同于你的数字身份。在开发环境可以为了方便不加密但在任何接近生产的环境务必使用密码加密私钥并严格限制其访问权限。规划好SAN列表在创建证书前想清楚这个服务可能会通过哪些地址被访问域名、IP、本地环回一次性在SAN中配置齐全避免反复重新生成和部署证书。考虑自动化与续期对于需要长期运行的内网服务可以编写脚本自动化证书的生成和部署。虽然自签名证书可以设置很长的有效期但出于安全习惯建议设置合理的有效期如1年并建立续期流程。探索更专业的本地CA工具如果你需要为大量本地开发项目颁发证书手动管理会很繁琐。可以考虑使用更专业的工具如mkcert它能自动创建本地信任的根CA并为任意域名一键生成受浏览器信任的证书极大提升开发体验。但理解其背后的OpenSSL原理能让你在遇到问题时更加从容。希望这篇详尽的指南能帮你扫清HTTPS本地开发的障碍。当你再次看到那个绿色的锁图标时你会知道这背后是一套清晰、安全的标准在运作而你已经掌握了驾驭它的钥匙。