资讯动态

麒麟操作系统HTTPS配置:自建CA与Nginx证书链落地实践

发布时间:2026/10/23 2:37:56 来源:尧图企业网站定制
1. 先搞清楚麒麟操作系统上做HTTPS配置到底难在哪这个系列写到第十一篇前面十篇把服务架构里的目录规划、服务编排、日志规范、反向代理、静态资源分发这些基础环节都铺完了轮到HTTPS这一块很多人的第一反应是装个证书改两行配置的事。真到麒麟操作系统上落地才发现麻烦点不在改两行而在整条链路证书从哪来、私钥怎么放、密码套件怎么收敛、防火墙和SELinux怎么配合、国密算法要不要上、服务起来了怎么验证握手真的对。我这个HTTPS配置的完整流程是在几个内网服务项目里反复推倒重来过之后才定型的中间踩过的坑够写一篇。先说清楚这篇的定位它面向的是已经把服务跑在麒麟操作系统上、现在需要把HTTP升级成HTTPS的人不管你是第一次配Nginx的新手还是手上有一堆证书要统一管理的运维都能直接照着做。文中所有路径、参数、命令都是我在麒麟V10服务器版上实测过的涉及发行版差异的地方我会单独标出来。整套内容分六块为什么要这么设计、方案怎么选、证书体系怎么搭、服务端配置怎么写、上线后怎么验证加固、出问题怎么查。跟着走一遍一套可用的HTTPS服务架构就出来了。提示本篇是系列文章的第十一篇默认你已经完成前面章节里的服务目录规范、反向代理和日志目录规划。如果你是从这一篇开始看的建议至少先把Nginx的安装目录和日志路径对齐否则后面配置的路径会对不上。1.1 一次证书过期引发的整条链路复盘我印象最深的一次是某个内部管理系统早上八点半集体打不开浏览器直接甩出证书过期警告。表面看是证书没续往下挖才发现三个问题叠在一起第一证书是用某台机器上的自签脚本签的脚本本身没纳入版本管理谁也不知道根证书私钥放在哪第二服务端配置里只放了服务器证书没放完整证书链某些客户端本来就走不通只是之前用HTTP访问没暴露第三到期前没有任何监控全靠人工记日子。这件事之后我给自己定了三条铁律后面所有HTTPS落地都按这个来证书签发流程必须可复现也就是配置文件、脚本全部入库服务端只引用固定的证书目录不允许随手往任意路径丢文件证书有效期必须进监控到期前三十天开始告警。这三条听起来像废话但真正能省掉的事故远比想象的多。尤其在内网环境很多人觉得反正就自己人用随便签一个结果一旦某个下游系统换成严格校验证书的客户端整条调用链就断了。1.2 麒麟操作系统与常见发行版在HTTPS配置上的实际差异麒麟服务器版走的是RPM系包管理yum/dnf都能用这和常见的CentOS系操作习惯接近但它自带的OpenSSL版本、SELinux默认策略、以及部分目录约定有自己的特点配置的时候有几个点必须注意。第一个是 OpenSSL 版本。麒麟V10上执行openssl version -a一般能看到 1.1.1 系列这意味着 TLS 1.3 是原生支持的配置里可以直接开TLSv1.3。但如果你的环境是较早的小版本或者是某些定制镜像可能会遇到 1.0.2 系列的残留那就只能停在 TLS 1.2而且部分现代密码套件用不了。动手之前先确认版本别写完配置重启才发现指令不认。第二个是 SELinux。麒麟默认大概率是 enforcing 状态getenforce一下就知道。Nginx 读/etc/nginx/ssl/下的私钥如果文件安全上下文不对日志里会出现Permission denied而ls -l看权限明明是 600这就是典型的 SELinux 拦截。这个坑我吃过不止一次。第三个是防火墙。麒麟默认启用 firewalld443 端口不开本地curl通、外部访问不通排查方向很容易跑偏到证书上。第四个是时间同步。证书有效期校验完全依赖系统时间麒麟装完系统如果没配 chrony机器时间可能偏几分钟服务端自己没事但客户端校验证书时会报证书尚未生效。这个错误提示极具误导性很多人第一反应是证书签发日期写错了。2. 整体设计与方案选型先把路线定死再动手HTTPS 这块最容易犯的错是边做边想证书随便签一个配置随手写一段跑通了就完事。等到要加第二个域名、要上双向认证、要做国密改造发现前面全是坑。我的习惯是先花半小时把方案定下来包括证书从哪来、算法用什么、服务端怎么组织配置、密钥怎么保管后面所有操作都按这个方案执行。2.1 证书来源的三条路线与适用边界证书来源基本就三条路商业CA签发、内部自建CA签发、以及公共免费CA。选哪条不看技术难度看使用场景。证书来源适用场景客户端信任成本有效期管理我的实际选择建议商业CA对公网开放、面向外部用户零成本系统内置根证书一年一续需走采购流程对外门户、有合规要求的业务内部自建CA内网服务、微服务间调用、测试环境需把根证书导入各客户端信任库自己定可批量重签内网占比八成以上首选公共免费CA个人站点、临时验证零成本90天需自动化续期快速验证或短期演示内网场景我几乎一律选自建CA原因很实在微服务之间互相调用如果每个服务都去申请商业证书成本和流程都不划算自建CA可以一次性把根证书推到所有服务器和终端的信任库里之后签发任意数量的服务器证书都是几分钟的事。代价是要管好根证书私钥这把钥匙丢了等于所有信任关系作废。注意自建CA的根证书私钥必须离线保存或者至少单独加密存放绝对不能和服务器证书放在同一台机器上。我见过把根私钥和服务器私钥放一个目录的等于把保险柜钥匙插在锁上。2.2 服务端软件选型Nginx、Apache还是国密专用网关服务端软件这块我的默认答案是 Nginx。理由是配置直观、性能足够、社区资料多而且麒麟的软件源里能直接装实在不行编译一遍也就是十来分钟。Apache 也能用配置指令更啰嗦一点但如果你现有服务就是 Apache 跑的没必要为了 HTTPS 换。真正的分水岭在于要不要上国密算法。如果业务侧明确要求 SM2/SM3/SM4 这套算法体系标准的 OpenSSL Nginx 组合就不够用了需要换用支持国密的 TLS 库或者用带国密补丁的 Nginx 分支。我在这块的实践经验是先确认需求再动手别一上来就折腾国密编译因为国密链路对客户端也有要求浏览器和调用方都得支持否则服务端配好了对面握不上手白忙一场。普通场景绝大多数内网服务走标准 RSA 或 ECC 证书就够了ECC 相比 RSA 的优势是密钥短、握手快同等安全强度下 P-256 相当于 RSA 3072握手时的计算量小很多。我在新项目里基本都用 ECC。2.3 密钥算法与密码套件的规划表算法选择直接决定后面的所有命令所以先定下来。下面这张表是我在不同场景下的实际选择算法类型密钥长度安全强度参考握手性能我的使用场景RSA2048相当于 112 位对称一般兼容老客户端不得已才用RSA4096相当于 140 位对称较慢握手耗CPU根CA密钥签发频率低可以接受ECCP-256相当于 128 位对称快服务器证书默认选择ECCP-384相当于 192 位对称中等高安全要求场景SM2256国密体系快有国密合规要求的业务密码套件的规划原则很简单只留 AEAD 类套件也就是带 GCM 或 ChaCha20-Poly1305 的把 CBC 模式的老套件全部砍掉优先 ECDHE 密钥交换保证前向安全性这样即使私钥某天泄露历史流量也解不开禁用 SSLv3、TLS 1.0、TLS 1.1这三个版本在今天已经属于必须清理的范畴。至于具体的套件字符串怎么写我在第 5 章会给可以直接抄的版本。3. 证书体系从零搭建CA、签发、格式转换一条龙这一章是整篇最重的部分也是最能体现工程化思维的地方。很多人签证书的方式是在服务器上敲一行openssl req -x509签完把文件一放就完事。这种方式在只有一台服务的时候没问题一旦要签十台、二十台就会出现签发参数不一致、SAN 字段漏写、有效期混乱的问题。所以我的做法是从一开始就建一套目录结构和配置文件所有签发动作走固定流程。3.1 目录规划与CA根证书的生成流程先定目录。我习惯把CA相关的东西放在/opt/pki下和服务的证书目录物理分开mkdir -p /opt/pki/{ca/private,ca/newcerts,server,client} chmod 700 /opt/pki/ca/private touch /opt/pki/ca/index.txt echo 1000 /opt/pki/ca/serialca/private目录权限给 700因为里面放的是根私钥index.txt和serial是 OpenSSL 的CA数据库文件用来记录签发历史后面每签一张证书都会往里追加记录出问题的时候能查到这张证书是谁签的、什么时候签的。接着写CA的配置文件/opt/pki/ca/openssl.cnf[ ca ] default_ca CA_default [ CA_default ] dir /opt/pki/ca certs $dir/certs new_certs_dir $dir/newcerts database $dir/index.txt serial $dir/serial private_key $dir/private/ca.key certificate $dir/certs/ca.crt default_md sha256 default_days 825 default_crl_days 365 policy policy_loose copy_extensions copy [ policy_loose ] commonName supplied organizationalUnitName optional organizationName optional countryName optional [ req ] distinguished_name req_distinguished_name default_bits 4096 prompt no x509_extensions v3_ca [ req_distinguished_name ] O Demo-IT OU Infra CN Demo-Root-CA [ v3_ca ] basicConstraints critical,CA:TRUE keyUsage critical,keyCertSign,cRLSign subjectKeyIdentifier hash这里有几个参数值得展开说。default_days 825是签发服务器证书的默认有效期取 825 天是因为主流浏览器对服务器证书的有效期上限是 398 天但内网自签不受这个限制825 天是一个兼顾不用太频繁换证和别签太长导致失控的折中值。copy_extensions copy很关键它保证签发时会把 CSR 里的 SAN 扩展原样复制到最终证书里否则你辛苦写的域名列表会被丢掉。CA 根证书的生成命令只有两行cd /opt/pki/ca openssl genrsa -out private/ca.key 4096 openssl req -new -x509 -days 3650 -key private/ca.key \ -sha256 -out certs/ca.crt -config openssl.cnf根证书有效期给 3650 天十年因为换根证书意味着所有客户端的信任库都要重新导入成本极高。私钥生成后立刻chmod 400并且强烈建议把 ca.key 拷到离线介质上一份然后考虑从在线机器上删掉——后续签发可以在一台隔离的签发机上做签发完只把证书拷出来。生成完用这条命令核对一下openssl x509 -in certs/ca.crt -noout -text | head -20确认Basic Constraints里是CA:TRUEKey Usage里有Certificate Sign这两项不对后面签出来的证书全都没法用于服务端认证。3.2 服务器私钥、CSR 生成与 SAN 扩展的正确写法服务器证书的私钥我用 ECCmkdir -p /opt/pki/server/app.demo.local cd /opt/pki/server/app.demo.local openssl ecparam -genkey -name prime256v1 -out server.key openssl req -new -key server.key -out server.csr \ -subj /ODemo-IT/OUInfra/CNapp.demo.local接下来是SAN扩展文件server.ext这个东西决定证书能被哪些地址使用basicConstraints CA:FALSE keyUsage critical,digitalSignature,keyEncipherment extendedKeyUsage serverAuth subjectAltName alt_names subjectKeyIdentifier hash authorityKeyIdentifier keyid,issuer [ alt_names ] DNS.1 app.demo.local DNS.2 *.demo.local IP.1 10.0.0.10这里有两个非常容易出错的地方。第一个是只写 CN 不写 SAN。现代浏览器和绝大多数 HTTP 客户端早就忽略了 CN 字段只看 SANCN 里写什么完全不影响校验证书是否匹配。所以就算你 CN 写对了SAN 里没这个域名照样报证书不匹配。第二个是通配符的作用范围。*.demo.local只能匹配一级子域a.b.demo.local是匹配不上的而且通配符不能用在顶级和二级位置上写*.local是无效的。内网服务我一般会把具体域名全列出来最多加一个*.demo.local兜底。另外如果这个服务有通过 IP 直接访问的需求IP.1这一行必须写而且客户端访问时用的 IP 必须和证书里的完全一致。签发命令cd /opt/pki/ca openssl ca -config openssl.cnf -extfile /opt/pki/server/app.demo.local/server.ext \ -in /opt/pki/server/app.demo.local/server.csr \ -out /opt/pki/server/app.demo.local/server.crt -batch用openssl ca而不是openssl x509 -req是为了让签发记录进index.txt将来能追溯。签完检查openssl x509 -in server.crt -noout -dates -subject -issuer -ext subjectAltName openssl verify -CAfile /opt/pki/ca/certs/ca.crt server.crt两条都通过说明证书链本身没问题。verify返回OK是硬指标不 OK 就别往下走。3.3 证书链打包、格式转换与客户端分发服务端配置里引用的证书文件必须是服务器证书中间证书拼接的完整链。自建CA直接签发的话没有中间证书只需要服务器证书本身但如果是商业证书或者两级CA结构就必须拼接cat server.crt intermediate.crt fullchain.crt顺序必须是服务器证书在前中间证书在后反了会导致客户端拿不到签发链握手失败。这个顺序错误极其常见而且错误提示往往是证书链不完整这类偏模糊的表述排查起来很耗时。格式转换的需求主要来自两类客户端Java 系的应用通常需要 JKS 或 PKCS12Windows 的某些工具喜欢.pfx。转换命令# 转 PKCS12含私钥注意保护 openssl pkcs12 -export -in fullchain.crt -inkey server.key \ -out app.demo.local.p12 -name app.demo.local # PKCS12 转 JKS 需要 JDK 的 keytool keytool -importkeystore -srckeystore app.demo.local.p12 \ -srcstoretype PKCS12 -destkeystore app.demo.local.jks \ -deststoretype JKS转出来的.p12和.jks里含私钥分发的时候要单独走安全通道绝不能和证书一起丢在公共目录里。同时别忘了分发根证书。所有需要访问这个服务的客户端都得把ca.crt导入信任库否则再正确的证书也会被判定为签发者未知。Linux 客户端的导入方式是cp ca.crt /etc/pki/ca-trust/source/anchors/demo-root-ca.crt update-ca-trust extractWindows 走受信任的根证书颁发机构导入浏览器和系统级应用会自动继承。这一步做完客户端访问就不会再弹证书警告了。3.4 国密 SM2 证书的签发路径有国密要求的场景标准 OpenSSL 走不通需要额外的工具链。麒麟系统上一般通过 GmSSL 或者带国密支持的 TLS 库来完成。SM2 证书的生成流程和 RSA 结构类似但命令不同gmssl sm2keygen -pass 123456 -out sm2.key -pubout sm2pub.key gmssl certgen -C XX -ST XX -L XX -O Demo-IT -OU Infra \ -CN app.demo.local -days 825 \ -key sm2.key -pass 123456 -ca -cacert ca.crt -cakey ca.key \ -out sm2.crt这里的-C、-ST、-L按单位规范填写即可内网自签时这几个字段的实际意义不大但字段不能为空。签完之后服务端配置需要换成支持国密的 Nginx标准的ssl_certificate指令不一定支持 SM2 证书具体要看编译时带的是哪个 TLS 库。提示国密链路要求客户端也支持 SM2 套件普通浏览器默认不开启。做完服务端配置后一定要用支持的客户端实际握一次手验证不能只看配置文件语法通过就上线。4. Nginx 与 Apache 的 HTTPS 配置实操证书齐了进入服务端配置。这一章给的是可以直接抄的配置但每个参数我都会说明为什么这么写因为参数背后是性能和安全性的权衡照抄不理解出了问题就改不动。4.1 先确认编译参数与依赖是否齐全在麒麟上装 Nginx第一件事是看现有的 Nginx 有没有带 SSL 模块nginx -V 21 | tr \n | grep -E ssl|http_v2如果输出里有--with-http_ssl_module直接改配置就行如果没有就得重装或重新编译。源码编译的完整依赖安装yum install -y gcc gcc-c make pcre-devel zlib-devel openssl-devel编译参数我通常这么给./configure --prefix/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-stream \ --with-stream_ssl_module \ --with-http_gzip_static_module--with-http_realip_module在反向代理场景下必须带否则日志里全是代理的 IP排查问题基本抓瞎。--with-stream_ssl_module是为四层 TLS 透传预留的后面加 TCP 类的服务会用到。编译前跑一下openssl version确认 TLS 1.3 支持Nginx 的TLSv1.3配置项依赖 OpenSSL 1.1.1 以上。4.2 Nginx 的 HTTPS server 块逐行拆解先建目录并放好文件mkdir -p /etc/nginx/ssl/app.demo.local cp fullchain.crt /etc/nginx/ssl/app.demo.local/ cp server.key /etc/nginx/ssl/app.demo.local/ chmod 600 /etc/nginx/ssl/app.demo.local/server.key chown -R root:root /etc/nginx/ssl完整配置server { listen 443 ssl; http2 on; # Nginx 1.25.1 之后的新写法 server_name app.demo.local; ssl_certificate /etc/nginx/ssl/app.demo.local/fullchain.crt; ssl_certificate_key /etc/nginx/ssl/app.demo.local/server.key; ssl_trusted_certificate /etc/nginx/ssl/app.demo.local/fullchain.crt; 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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 10.0.0.2 valid300s; resolver_timeout 5s; add_header Strict-Transport-Security max-age31536000 always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options SAMEORIGIN always; location / { root /data/www/app; index index.html; try_files $uri $uri/ /index.html; } access_log /var/log/nginx/app_ssl_access.log main; error_log /var/log/nginx/app_ssl_error.log warn; }几个参数值得单独说。listen 443 ssl后面的ssl不能丢丢了端口开了但不会走 TLS 握手。http2 on是新版本写法老版本1.25.1 之前要写成listen 443 ssl http2;写法混用会直接启动失败。ssl_ciphers这段只留了 ECDHE AEAD 的组合去掉了所有 CBC 套件。CBC 模式在 TLS 1.0/1.1 时代有已知的填充攻击面虽然 TLS 1.2 已经改善但既然能砍就砍掉现代客户端没有不支持的。ssl_prefer_server_ciphers on让服务端的选择优先于客户端配合上面这段套件列表实际效果就是强制走我们指定的高强度组合。ssl_session_cache shared:SSL:10m的大小是有讲究的1MB 大约能存 4000 个会话10MB 对应约 4 万个。会话复用能省掉完整的非对称握手对短连接多的服务提升非常明显。如果你的服务 QPS 很高、连接数大可以调到 20m但也就到顶了再大收益递减。ssl_session_tickets off是我个人的选择。Ticket 机制虽然也能省握手但密钥管理在服务端不做轮换的话存在一定风险内网场景直接用 session cache 更可控。ssl_stapling这一组依赖 OCSP 服务器可达内网环境如果出不去外网这一组可以整段注释掉否则会拖慢握手Nginx 会等 OCSP 响应超时。这是内网配置里一个很实际的分叉点。add_header ... always里的always很重要没有它4xx/5xx 响应不会带上这些头。HSTS 头尤其要注意一旦下发客户端会在有效期内强制走 HTTPS所以先在测试环境确认 HTTPS 完全正常再开 HSTS否则配置出错的时候用户连降级到 HTTP 都做不到。注意add_header在 location 块里一旦重新定义会完全覆盖 server 块里定义的同类头。这是 Nginx 的经典行为不是 bug。如果某个 location 里加了add_header Cache-Control上面那几行安全头就全丢了需要在 location 里重新写一遍或者用include抽成公共片段。HTTP 强制跳转单独一个 server 块server { listen 80; server_name app.demo.local; return 301 https://$host$request_uri; }用 301 而不是 302让浏览器缓存跳转关系后续请求直接走 HTTPS少一次往返。4.3 Apache 的对应配置与迁移对照如果现有服务是 Apache 跑的不想换HTTPS 配置对应关系大概是这样VirtualHost *:443 ServerName app.demo.local SSLEngine on SSLCertificateFile /etc/httpd/ssl/app.demo.local/fullchain.crt SSLCertificateKeyFile /etc/httpd/ssl/app.demo.local/server.key SSLProtocol -all TLSv1.2 TLSv1.3 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 SSLHonorCipherOrder on SSLSessionCache shmcb:/run/httpd/ssl_scache(512000) Header always set Strict-Transport-Security max-age31536000 DocumentRoot /data/www/app /VirtualHost几个关键差异Apache 的协议控制用-all TLSv1.2这种加减语法容易写错成SSLProtocol all -SSLv3结果把 TLS 1.3 也带进来了会话缓存用的是shmcb共享内存机制512000是字节数安全头要mod_headers模块没装的话Header指令会直接报语法错误。装模块用yum install -y mod_ssl麒麟源里是有的。4.4 系统层面必须同步处理的三件事配置写完服务起不来八成是系统层面没放行。这三件事我每次部署都按顺序做一遍。防火墙firewall-cmd --permanent --add-port443/tcp firewall-cmd --permanent --add-servicehttp firewall-cmd --reload firewall-cmd --list-all最后一条确认 443 真的在列表里不要只看命令返回成功。SELinux先看状态getenforce。如果是 Enforcing需要让 Nginx 能读证书目录semanage fcontext -a -t httpd_config_t /etc/nginx/ssl(/.*)? restorecon -Rv /etc/nginx/ssl setsebool -P httpd_can_network_connect 1semanage来自policycoreutils-python-utils没装的话先装。如果不想动 SELinux 策略临时用setenforce 0验证一下问题是否出在这里但生产环境不建议长期关着。时间同步yum install -y chrony systemctl enable --now chronyd chronyc sources -v timedatectl statustimedatectl里看到System clock synchronized: yes才算完。证书有效期校验出错先查时间这是我排查顺序里排第二位的检查项排第一的是 DNS 和server_name。改完配置启动nginx -t systemctl reload nginx ss -lntp | grep 443nginx -t通过不代表配置没逻辑问题它只做语法检查所以后面还得实际握手验证。5. 上线后的验证、压测与安全加固配置能起来只是第一步真正判断这套 HTTPS 是否可用得靠实际握手和验证。这一章给的方法都是我在上线检查清单里固定保留的。5.1 用三条命令完成握手验证第一条测试 TLS 握手本身openssl s_client -connect 10.0.0.10:443 \ -servername app.demo.local \ -CAfile /opt/pki/ca/certs/ca.crt /dev/null重点看三处输出Verify return code: 0 (ok)说明证书链校验通过Protocol显示实际协商的版本应该是 TLSv1.2 或 TLSv1.3Cipher显示协商出的套件应该在上面配的白名单里。这三项任何一个不对说明配置没生效。第二条用 curl 走完整 HTTP 请求curl -v --cacert /opt/pki/ca/certs/ca.crt https://app.demo.local/health关注SSL certificate verify ok和响应状态码。--cacert指定根证书模拟的是已经导入根证书的客户端行为。第三条检查证书内容是否和预期一致openssl x509 -in /etc/nginx/ssl/app.demo.local/fullchain.crt \ -noout -subject -issuer -dates -ext subjectAltName把 SAN 列表逐项对照实际访问用的域名和 IP一项不匹配就是隐患。如果要验证双向认证服务端加ssl_client_certificate和ssl_verify_client on客户端用curl -v --cert client.crt --key client.key --cacert ca.crt https://app.demo.local/5.2 协议版本与密码套件的收敛验证配置里写了只允许 TLS 1.2 和 1.3得实际验证老版本确实被拒openssl s_client -connect app.demo.local:443 -tls1_1 /dev/null这条应该报握手失败。如果居然成功了说明ssl_protocols没生效回头检查是不是有多个 server 块重复定义了、或者配置改完没 reload。验证套件白名单可以用指定套件强制握手openssl s_client -connect app.demo.local:443 \ -cipher AES128-SHA /dev/nullAES128-SHA是 CBC 老套件应该被拒。这类反向验证比只看正向成功更有说服力因为配置里漏写一个参数的时候正向请求照样成功只有反向测试才能发现。如果有多种客户端需要兼容可以写个循环脚本把常见套件组合都跑一遍输出成表格for c in ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 AES128-SHA AES256-SHA3; do echo -n $c - echo | openssl s_client -connect app.demo.local:443 -cipher $c 2/dev/null \ | grep -q Cipher is ${c} echo 支持 || echo 已禁用 done5.3 性能调优会话复用、链长度与 CPU 开销HTTPS 的性能开销集中在两个地方握手时的非对称运算和传输时的对称加解密。后者硬件加速已经解决得差不多了主要优化握手。会话复用的效果可以实测。用openssl s_time做对比# 不复用 openssl s_time -connect app.demo.local:443 -new -time 10 -www /health # 复用 openssl s_time -connect app.demo.local:443 -reuse -time 10 -www /health正常情况下复用模式的每秒连接数应该是不复用的 3 到 5 倍。如果差距很小说明 session cache 没生效检查ssl_session_cache是否配在正确的 server 块里、worker 数量是否和缓存不匹配。证书链长度也是一个容易忽略的性能点。握手时服务端会把整条证书链发给客户端如果链里有四五个中间证书握手包可能超过 TCP 初始拥塞窗口导致多一个 RTT。我的经验是把链控制在两级以内中间证书和服务器证书合并根证书不放进链里客户端的信任库里已经有了这样总长度一般在 3KB 以内单包就能发完。ECC 证书相比 RSA 的收益也值得实测。同一个服务换成 P-256 之后握手耗时通常能降 40% 左右QPS 高的服务感受明显。代价是老客户端兼容性如果确实有 XP 时代的客户端要接那还是得用 RSA 2048。5.4 证书到期监控与自动化轮换最后一件必须做的事把证书到期纳入监控。我写过一个很简单的检查脚本挂在定时任务里每天跑#!/bin/bash CERT/etc/nginx/ssl/app.demo.local/fullchain.crt WARN_DAYS30 end$(openssl x509 -in $CERT -noout -enddate | cut -d -f2) end_ts$(date -d $end %s) now_ts$(date %s) days_left$(( (end_ts - now_ts) / 86400 )) if [ $days_left -lt $WARN_DAYS ]; then echo 证书剩余 ${days_left} 天到期请尽快续签 | mail -s 证书告警 opsdemo.local fi更省事的做法是用openssl x509 -checkend直接传秒数返回 0 表示还没到期openssl x509 -in $CERT -noout -checkend $((86400*30)) || echo 30天内到期续签流程要提前演练。自建CA续签就是重新签一张同名证书然后替换文件、nginx -s reload整个过程不需要停机。但替换前务必先nginx -t证书格式错误会导致 reload 失败如果旧进程已经停了服务就直接中断。提示reload 是平滑的但前提是新配置语法正确。所以流程固定成签发新证书到临时目录 → 语法检查并指向临时文件做一次测试 → 替换正式文件 → reload → 验证握手。中途任何一步失败都能回滚。6. 踩坑实录与常见问题速查这一章是我这几年攒下来的问题清单每一条都对应真实出现过的场景。放在最后是因为很多问题只有在实战里才会撞上前面讲原理的时候提一嘴容易记不住配着症状一起看才有效。6.1 常见问题速查表现象大概率原因排查命令处理方式浏览器报签发者未知客户端未导入根证书openssl s_client -CAfile ca.crt导入根证书到信任库浏览器报证书与域名不匹配SAN 里没写该域名openssl x509 -ext subjectAltName重新签含正确 SAN 的证书客户端报证书链不完整只放了服务器证书openssl s_client -showcerts拼接 fullchain 后再引用服务启动报 unknown directive ssl编译时没带 SSL 模块nginx -V重新编译带--with-http_ssl_module本地 curl 通外部不通防火墙未放行firewall-cmd --list-all开放 443 端口日志报 Permission denied 但权限是 600SELinux 上下文不对ls -Z、ausearch -m avcrestorecon或配置策略握手报 no ciphers available套件列表写错或与协议不匹配openssl ciphers -v检查ssl_ciphers字符串提示证书尚未生效系统时间偏差timedatectl、chronyc sources校准时间HTTPS 页面里资源加载失败混合内容页面里还有 http 引用浏览器控制台站内资源改为相对协议或 https响应头里没有 HSTSlocation 覆盖了 add_headercurl -Ilocation 里补写或用 include6.2 三个最容易反复踩的细节第一个是server_name与实际访问名不一致导致的空证书。Nginx 在 443 端口上如果没有匹配到server_name会用默认 server 块的证书结果是握手成功了但证书是别的域名的报不匹配。这种情况在多虚拟主机场景特别常见尤其有人喜欢把第一个 server 块当默认块用。解决办法有两个要么显式写listen 443 ssl default_server在一个专门的兜底块上要么确保每个域名都有独立的 server 块且server_name写全包括别名。第二个是私钥格式不匹配。用 OpenSSL 3.x 生成的私钥默认是 PKCS#8 格式文件开头是-----BEGIN PRIVATE KEY-----老版本是 PKCS#1开头是-----BEGIN RSA PRIVATE KEY-----。如果服务端软件版本较老可能会读不了 PKCS#8报bad decrypt或者干脆说私钥无效。转换命令# PKCS#8 转 PKCS#1 openssl rsa -in new.key -traditional -out old.key反过来转 PKCS#8 用openssl pkcs8 -topk8。这个坑很隐蔽因为文件内容看着都像密钥报错信息也不直白。第三个是证书和私钥不配对。手工管理证书的时候很容易出现证书换了但私钥没换或者两个服务的文件搞混。校验方法很简单openssl x509 -noout -pubkey -in server.crt | openssl md5 openssl pkey -pubout -in server.key | openssl md5两个 md5 一致才算配对不一致的配置能启动但握手必然失败而且报错信息通常只说SSL_CTX_use_PrivateKey 失败得自己往回找。6.3 我个人的几条实操心得证书目录的命名我坚持用域名而不是业务名。用业务名的问题是同一个业务可能有多个域名或者域名换了业务名没换时间一长就对不上。用域名命名ls一下就知道每个目录对应哪个站openssl x509 -subject一校就知道有没有放错。配置里引用证书路径我一律写绝对路径不用相对路径。Nginx 的相对路径是相对 prefix 的prefix 又是编译时定的换个安装方式路径就变了这类问题排查起来纯属浪费时间。还有一条是关于测试环境的。测试环境千万不要用生产证书哪怕只是临时验证。我见过测试机上放了生产证书然后测试脚本把配置推到生产两边的文件名一样但内容不同最后谁也说不清线上跑的是哪张证书。做法是测试环境用独立的子CA签发openssl x509 -issuer一眼能区分。最后说个上线节奏上的经验。HTTPS 切换不要一次性全量切先切一个访问量小的服务观察一周的握手失败率、CPU 占用、以及客户端的兼容反馈确认没问题再推其余服务。切换的时候保留 HTTP 端口一段时间做跳转而不是直接关掉给那些还在用硬编码 HTTP 地址的下游系统留缓冲期等日志里 HTTP 请求降到零了再关。这套节奏在几个项目里用过比一刀切省掉了很多夜里的电话。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑