内网HTTPS部署:用openssl自签名证书解决Chrome“不安全”提示

发布时间:2026/9/16 20:09:18
内网HTTPS部署:用openssl自签名证书解决Chrome“不安全”提示 内网部署HTTPS用openssl自签名证书一次搞定Chrome“不安全”提示先说说我为什么折腾这事。公司内网有套业务系统一直走HTTP后来要对接一些对安全性有硬性要求的接口加上审计也盯得紧必须上HTTPS。公网证书想都不用想内网域名和IP都不在CA的签发范围内唯一靠谱的方案就是自己用openssl做自签名证书。但问题来了证书一装Chrome就开始搞事打开页面直接一个大大的“不安全”要么就是“您的连接不是私密连接”点进去还要强行绕过才能访问。这种体验别说业务方接受不了自己看着都糟心。这篇文章就是我完整踩过一遍坑之后的实操总结。我会把自签名证书的生成、Nginx配置、Chrome信任证书这整条链路都讲透包括为什么自签名证书会被浏览器拒绝、怎么让Chrome真正“认”这张证书、以及在多域名/IP场景下怎么避免反复弹警告。整个过程不需要公网域名不需要花钱前后花十几分钟就能跑通。适合内网运维、开发环境调试、以及被各种测试环境HTTPS折腾过的人参考。openssl自签名证书再啰嗦一遍这是内网环境开启HTTPS的最佳路径也是整篇文章的核心。1. 方案选型为什么是自签名证书而不是内网CA1.1 自签名证书与私有CA的关系很多人一听到“自签名证书”第一反应就是临时用用、不正规。这个理解没错但不全面。自签名证书真正的问题不是“自己签”而是“没有权威背书”浏览器不知道该不该信它所以默认不信任。但这里有一个细节值得展开自己用openssl签一张根证书再拿它去签发服务器证书这也算“自签名”的范畴但效果比单纯一张服务器证书要好得多。为什么因为你可以把这张根证书导入操作系统的“受信任的根证书颁发机构”里。根证书被信任之后由它签出来的所有服务器证书都会自动被信任后续加机器、换域名、续期都不用重新往浏览器里导证书。这个思路本质上就是搭建了一个内网私有CA只不过规模小、流程简化了而已。我在内网环境里就是这么干的。先做一张Root CA证书只在内网使用专门用来签发各业务服务器的证书。别嫌这一步多余它省掉的麻烦远超你的想象。1.2 单张自签名证书与根证书方案怎么选如果你只有一台服务器、一个域名永远不打算扩展那直接用一张自签名服务器证书也不是不行。生成、配置、导入三步走完简单直接。但凡是有点扩展打算的哪怕只是未来可能会加一台测试机我都建议走根证书方案。这里有一个非常实际的问题单张自签名证书如果之后需要换域名或者换IP你只能重新生成、重新部署、重新导入信任老证书还得从浏览器里删掉否则新旧证书同时在信任列表里审计看着都难受。而根证书方案里服务器证书换了就换了根证书纹丝不动浏览器对它签发的任何新证书都天然信任。我自己的经验是内网环境折腾一次不容易与其省那几分钟不如一步到位做根证书方案。反正生成根证书也就比生成普通证书多敲一两行命令的事。注意如果公司有内网合规要求建议先确认一下私有CA是否允许使用。有些安全规范要求所有证书必须由统一CA签发私自搭私有CA可能会被审计指出。2. openssl生成证书命令背后的原理与细节2.1 环境准备与openssl版本检查在开始之前先确认机器上openssl是好的。不同系统的openssl版本差异不大但命令参数在某些老版本上会有细微区别。我这边用的是Ubuntu 20.04自带的OpenSSL 1.1.1f同时也在CentOS 7上用OpenSSL 1.0.2k跑通过同一套流程所以下面的命令在两个主流版本上都能用。Windows上也能操作建议直接用Git自带的openssl纯命令行工具链比较统一。无论哪个系统先跑一句openssl version看到OpenSSL版本号输出就说明环境没问题。如果提示找不到命令Linux上装一下opensslWindows上检查一下Git的bin目录是否在PATH里。这一步虽然基础但真有人折腾了半天最后发现是命令没敲对。2.2 生成根证书Root CA根证书是整个信任链的锚点它的私钥一定要保护好。我是在内网专门的目录里操作的后续所有证书材料都按目录归类别全部堆在一个目录里否则几个月后自己都找不到哪个是哪个。先生成根证书私钥mkdir -p ~/certs/root cd ~/certs/root openssl genrsa -out rootCA.key 2048这里我用的是2048位RSA单位是bit2048是当前的主流选择。往下走用这根私钥自签一张根证书openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt这个命令会交互式问你一堆信息Country Name、State、Organization这些。重点只有一个Common Name我建议填一个能一眼认出来的名字比如“Internal Root CA”。证书到期时间我给了10年内网根证书免得太频繁折腾3650天是个合适的值。关于-nodes参数可能要解释一句它的意思是“私钥不加密”。如果不加openssl会要求你设置一个私钥口令之后每次使用私钥都要输入口令对自动化部署和Nginx启动来说是个麻烦。内网环境里私钥文件本身做好权限管控就是没必要额外加密。2.3 生成服务器证书重点SAN扩展接下来是为业务服务器生成证书。这里有一个关键点必须加上SANSubject Alternative Name扩展否则Chrome会以“不安全的证书”拒绝连接。早期版本浏览器看Common Name就够了现在Chrome和Firefox都强制校验SAN没有SAN的证书一律不认。先看一下我用的openssl配置文件这一步别偷懒写好配置能让后面所有命令执行得更顺利。配置文件内容如下我把它保存成server.conf[req] default_bits 2048 prompt no default_md sha256 distinguished_name dn req_extensions req_ext [dn] C CN ST Beijing L Beijing O Internal OU IT CN web.internal.local [req_ext] subjectAltName alt_names [alt_names] DNS.1 web.internal.local DNS.2 localhost IP.1 192.168.1.100这个配置里最重要的就是[alt_names]这一段。DNS.1是内网域名IP.1是内网IP。这里有个坑要提醒如果访问方式是用IP比如直接访问https://192.168.1.100那么证书里必须包含IP.1这一项光写DNS是不行的。反过来也一样如果访问方式是域名DNS必须写。生成服务器私钥和证书签名请求CSRcd ~/certs/server openssl genrsa -out server.key 2048 openssl req -new -key server.key -config server.conf -out server.csr然后用根证书签发这张服务器证书openssl x509 -req -in server.csr -CA ~/certs/root/rootCA.crt -CAkey ~/certs/root/rootCA.key -CAcreateserial -out server.crt -days 825 -sha256 -extensions req_ext -extfile server.conf这里解释两个参数。-days 825是证书有效期825天是Chrome对受信任证书的最长有效期要求超过这个天数Chrome会提示“证书有效期过长”并拒绝信任。这个限制是2020年之后Chrome版本引入的很多人栽在这里。-extfile server.conf指定了SAN扩展从哪个配置文件读取这步必不可少少了它证书就没有SANChrome照样不认。看到Signature ok和Getting CA Private Key的输出说明证书签发成功。2.4 证书格式与常见转换需求生成的server.crt是PEM格式Nginx、Apache等大多数服务端软件直接就能用。但有些场景需要换个格式比如Windows的IIS要PFX某些Java服务要JKS或者有的客户端需要cer格式。最常用的是把crt和key合成PFXopenssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt这条命令会要求设置导出密码可以设成空密码但Windows导入pfx时要求密码不能为空建议还是设一个简单的。如果服务端只接受包含完整链的证书文件还要把根证书和服务器证书串在一起cat server.crt rootCA.crt server_fullchain.crt这种fullchain文件在有些Nginx配置里比单独一张服务器证书更稳妥因为它把信任链带全了浏览器可以一路往上找根证书完成校验。3. 部署HTTPS以Nginx为例的完整配置3.1 生成Diffie-Hellman参数增强安全性有人可能觉得证书都生成了直接配置Nginx不就行了还要搞什么DH参数这里牵扯到一个实际问题Nginx默认的SSL配置如果使用Diffie-Hellman密钥交换而你没有提供DH参数文件性能和安全强度都不理想。为了拿到A评分也为了消除Chrome控制台里可能的警告我建议生成一份2048位的DH参数openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048这一步会耗时比较久VPS上大概要跑几分钟在内网机器上也许更快。可以趁机喝口水不用盯着终端。之前听说过有人用4096位DH参数生成时间翻好几倍而2048位在安全性和性能之间已经足够内网环境没必要追求过高位数。3.2 Nginx站点配置与SSL参数说明假设你的nginx已经安装好了进入站点配置目录我习惯在/etc/nginx/conf.d/下面建一个ssl.conf。以下是我实际使用的配置模板可以直接替换域名和路径server { listen 80; server_name web.internal.local 192.168.1.100; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name web.internal.local 192.168.1.100; ssl_certificate /etc/nginx/ssl/server_fullchain.crt; ssl_certificate_key /etc/nginx/ssl/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_dhparam /etc/ssl/certs/dhparam.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }这段配置里做了三件事。第一段server块把HTTP请求301重定向到HTTPS这样用户不管输入http还是https都会走安全通道。第二段server块监听443端口启用HTTP/2协议配置证书、协议版本和加密套件。location块是反向代理的配置把443进来的请求转发到内网后端的8080端口服务同时把客户端原始IP和协议信息通过Header传给后端。X-Forwarded-Proto $scheme这一行容易被忽略但很重要。后端应用需要判断请求是HTTP还是HTTPS如果没传这个Header应用可能误判请求来源是HTTP在生成绝对链接时搞出“https页面里混着http链接”的怪异现象。配置完检查一下语法nginx -t如果输出syntax is ok和test is successful重启Nginxsystemctl restart nginx3.3 防火墙与端口检查新装的服务经常在防火墙这层翻车。Nginx配置没问题证书也没问题但外部就是访问不了——十有八九是443端口没放行。在服务器上确认一下监听状态ss -tlnp | grep 443看到LISTEN状态并且进程是nginx就说明服务起来了。如果本机防火墙开着放行443firewall-cmd --permanent --add-servicehttps firewall-cmd --reloadCentOS之外Ubuntu如果启用了ufw就用ufw allow 443/tcp。内网环境还要注意主机之间的安全组策略云上叫安全组虚拟化环境可能叫端口组总之把443端口在两个方向都放行。4. Chrome信任自签名证书彻底消除“不安全”警告4.1 为什么Chrome会提示“不安全”Chrome对证书的校验逻辑可以简单理解为三道关卡。第一证书是否由受信任的CA签发第二证书是否在有效期内第三证书中的域名或者IP是否和地址栏匹配。任何一道关卡不过页面就会显示不安全。自签名证书通常三道关卡全不过签发者不在系统信任列表里有效期又设得随意SAN更是经常没有。所以Chrome直接给出大红色警告。明白了校验逻辑解决方案也就清晰了让证书满足这三道关卡的要求。也就是把根证书装进系统信任库有效期控制在825天以内SAN里写清楚域名和IP。这三步做完Chrome的警告自然消失。4.2 在Windows上将根证书导入受信任区以Windows 10/11为例操作路径是打开“运行”输入certmgr.msc进入“证书管理器”展开“受信任的根证书颁发机构”右键“证书”文件夹选择“所有任务”-“导入”。导入向导走到“证书存储”那一步选择“将所有的证书都放入下列存储”浏览到“受信任的根证书颁发机构”。确认后系统会弹安全警告提示“即将把该证书添加到根存储区”点“是”即可。这里容易犯的错是导入到“个人”存储区。个人存储区只是告诉Windows“我有这么张证书”但不会被用来做信任判断。必须放到“受信任的根证书颁发机构”下Chrome才会认。导入完成后重启浏览器。别开着浏览器导入Chrome的证书缓存不会那么快刷新重启能省很多排查时间。4.3 Linux和移动端证书导入方法Linux上信任根证书的路径因发行版而异。以Ubuntu为例把rootCA.crt拷贝到/usr/local/share/ca-certificates/然后运行sudo update-ca-certificatesCentOS则需要把证书放到/etc/pki/ca-trust/source/anchors/然后运行sudo update-ca-trust执行完可以用openssl verify验证一下信任链是否建立成功openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt ~/certs/server/server.crt显示ok就说明系统层面已经信任了。Chrome在Linux上读的也是系统证书库系统信任了Chrome就没问题。手机端是另一回事。iOS和Android对用户导入的根证书有不同的限制策略Android 7以上对于系统应用才信任用户安装的CA普通App默认不信任用户CA需要在App的网络安全配置清单里显式允许。内网场景如果只涉及浏览器访问那就用浏览器打开HTTPS地址按系统提示安装证书即可但测试时尽量用Chrome/Firefox别依赖系统默认浏览器避免奇奇怪怪的问题。4.4 重新打开页面验证效果证书导入、浏览器重启后访问https://web.internal.local。地址栏应该直接显示小锁图标点击锁图标可以看到证书详情其中“颁发者”一栏显示的是我们创建的Internal Root CA。如果依然提示不安全按F12打开开发者工具切到“Security”标签页这里会明确告诉你证书校验失败的原因。我遇到的最常见提示是“Certificate is not trusted”和“Certificate name mismatch”。前者是根证书没导入或者导入位置不对后者是SAN里缺了当前访问的域名或IP。对着问题去排查基本几分钟就能定位。5. 实操中踩过的坑排查思路与问题速查5.1 证书有效期导致的不信任问题我在最初部署时把服务器证书有效期设置成了3650天也就是10年。导入根证书后访问Chrome还是提示“您的连接不是私密连接”点开详细信息才发现证书本身被判定为“证书有效期过长”。这个问题在网上讨论得很多核心原因是Chrome在2020年9月开始执行“受限证书有效期”策略公网证书最长398天而内网私有CA签发的证书也被浏览器视为“不受信任CA”同样受这个策略限制。虽然实际执行时有些版本会宽松一点但我见到的最保险做法是825天。825天这个数字不是随便定的。Chrome要求证书有效期不超过398天其实针对的是公网公开受信CA私有CA证书一般会被校验“过期日期在2035年之前并且有效期不超过15年”。但实测下来825天在Chrome和Firefox里都不会弹“有效期过长”的警告。如果你想要更保守直接把有效期设为397天也行基本不会翻车。我生产环境里用的825天跑了很久没出问题。5.2 SAN缺失导致的域名不匹配资深一点的朋友可能还记得老版本Chrome在SSL证书校验时会看通用名字段Common Name后来逐步迁移到只看SAN。从Chrome 58开始Common Name基本被忽略证书没有SAN直接判为无效。我的第一张测试证书就是这种情况。当时用openssl req -x509直接生成自签名证书没有加任何扩展配置签完照样配置到Nginx上浏览器死活不信任。排查的过程很痛苦因为Chrome不给你具体原因只能看到“此服务器无法证明它是web.internal.local”。后来把证书里的SAN补上问题马上解决。所以再次强调生成证书时一定在配置文件里写上subjectAltName把会用到域名和IP都列进去。多个域名、多个IP都没问题用DNS.1、DNS.2、IP.1、IP.2的顺序依次往下加就行。5.3 存放路径和权限问题还有一个特别容易遇到但特别隐蔽的问题Nginx启动时提示cannot load certificate或者Permission denied。这不是证书内容有问题而是证书或私钥文件的权限不对Nginx的worker进程读取不了。检查一下文件权限ls -l /etc/nginx/ssl/server.crt /etc/nginx/ssl/server.key如果server.key对其他用户可读建议执行chmod 600 /etc/nginx/ssl/server.key chmod 644 /etc/nginx/ssl/server.crtSELinux的内网环境里还可能出现“Nginx无法读取证书”的问题。这种时候布告栏似的错误提示会让人很困惑因为文件权限完全正常。解决办法是检查SELinuxgetenforce如果返回Enforcing可以临时放行Nginx读取证书目录或者直接用audit2why查看具体拦截原因。内网环境没有特殊安全要求时把SELinux设为Permissive也不失为一种快速排除法但生产环境建议还是按SELinux规范处理。5.4 常见问题速查表现象常见原因处理方式Chrome提示证书不受信任根证书未导入系统信任库将rootCA.crt导入“受信任的根证书颁发机构”证书显示“有效期过长”服务器证书有效期超过限值重新签发证书有效期设为825天以内地址栏显示“不安全”且有NET::ERR_CERT_COMMON_NAME_INVALID证书SAN缺少当前域名或IP在server.conf的alt_names中补充对应项后重新签发Firefox可以访问但Chrome不行Chrome证书策略更严格按Chrome要求导入根证书并检查SANNginx启动报certificate错误证书文件路径或权限错误检查文件路径、chmod权限、SELinux状态页面加载后部分资源仍为http反向代理未传递HTTPS协议头配置proxy_set_header X-Forwarded-Proto $scheme后端API提示非法请求后端在HTTPS代理后未获取正确协议检查Web应用对X-Forwarded-Proto的解析配置这张表基本涵盖了我遇到过的绝大多数问题。严格来说不少问题是一环扣一环的比如SAN缺失容易和根证书没导入同时出现但排查时分开看反而更高效改一个验证一个别一次改三个变量再去看效果那样出了问题都不知道是哪步引起的。6. 一些值得知道的扩展场景6.1 多域名、多IP的证书再签发如果这次做完之后后续要增加新的内网域名或IP不需要碰根证书只要把server.conf里的[alt_names]补上新的条目重新生成一张服务器证书部署到对应机器上即可。早就信任了根证书的浏览器不需要再做任何操作新证书直接生效。这种“根证书一次性导入、服务器证书随便签发”的架构是私有CA的核心优势。内网环境里机器多了之后你会感谢自己当初没省那几分钟。6.2 纯内网IP场景的特别处理有些业务系统压根没有域名就靠IP访问。这种情况下证书的SAN里必须写IP而不能只写DNS。比如访问https://192.168.1.100那么server.conf里要有IP.1 192.168.1.100。还有极个别场景下需要让Chrome临时信任某个IP但浏览器并不推荐长久这么做。更好的做法还是在内网DNS里给服务器配上内部域名统一通过域名访问证书和域名一致问题少很多。我建议能上域名就上域名别拿IP硬扛。6.3 内网服务对接时的其他时间成本内网HTTPS不只是浏览器访问很多系统对接时也会校验证书。比如Java开发环境里Java进程有自己独立的信任库cacerts系统信任了根证书不代表Java进程也信任。需要在Java信任库里额外导入keytool -import -trustcacerts -alias internalroot -file rootCA.crt -keystore $JAVA_HOME/lib/security/cacerts默认密码是changeit导入后Java服务才能正常调用HTTPS接口。这个问题在开发环境尤其常见后端联调时对方报“证书验证失败”一查就是Java没信任你的根证书。不同的运行时、不同的开发语言里证书信任机制各不相同对接前先跟对方确认清楚能省很多来回。7. 写在最后的经验提醒从头到尾走一遍关键技术点其实就三个openssl生成证书时带上SAN扩展根证书导入系统信任库证书有效期不要超过浏览器限制。这三件事做好了内网HTTPS就能顺畅跑起来Chrome“不安全”的红色警告也能彻底消除。我自己在第一次配置的时候也走了不少弯路印象最深的就是那一张没有SAN的证书换了三个浏览器都打不开最后老老实实查文档才发现问题所在。回头想想这些坑在配置文档里其实都有提到只是没人把它们串成一条完整的操作链路。写这篇文章就是希望把这些经验整理好让后面的人少踩几个坑。最后再分享一个小技巧生成的证书和私钥文件最好统一放在一个目录里目录名带日期比如certs/2025-01-internal-ssl/。这样三个月后你想给新机器配证书不用在一堆文件里翻来翻去找。证书文件命名也尽量标准化rootCA、server这种一眼能看懂的比cert_1.crt这种命名好一百倍。如果后续有新的内网服务要上HTTPS打开这篇文章跟着走一遍就行整个过程不会超过二十分钟。祝各位部署顺利浏览器地址栏里永远挂着小锁。