资讯动态

Nginx国密改造实战:源码编译OpenSSL与双栈HTTPS配置

发布时间:2026/10/15 16:09:21 来源:尧图企业网站定制
1. 为什么要在编译层面折腾国密支持很多人第一次接触国密改造第一反应是找个现成的二进制包直接替换或者装个模块了事。但真到了生产环境尤其是金融、政务、能源这类对传输安全有硬性要求的场景你会发现光靠动态加载模块往往不够——要么版本对不上要么编译参数缺斤少两要么运行起来握手直接失败。所以“编译配置”这四个字才是国密改造里真正见功力的地方。这篇是系列的第二篇上一篇我们聊了国密算法的基础概念和整体改造思路这一篇直接进入硬核环节从源码编译入手把 Nginx 改造成一个能同时跑国际算法和国密算法的双栈服务。说白了就是让同一台服务器既能接普通浏览器的 HTTPS 请求也能接国密客户端的加密连接两边互不打架。这篇文章适合谁看如果你已经会基本的 Linux 编译操作懂 Nginx 配置文件的大致结构但一碰到国密就不知道从哪下手那这篇就是写给你的。我会把每一步为什么这么做、参数怎么算、坑在哪里全部摊开讲。全程不依赖任何特定厂商的闭源包纯靠开源组件和标准编译流程走通。先明确一个核心认知Nginx 本身并不原生支持国密算法它依赖 OpenSSL 提供的密码学能力。所以“编译 Nginx 支持国密”这件事本质上拆成两步——第一步是编译一个支持国密的 OpenSSL第二步是让 Nginx 链接这个 OpenSSL 重新编译。两步缺一不可顺序也不能反。注意国密改造涉及密码学组件的替换任何一步编译参数出错都可能导致握手失败或性能骤降。建议在测试环境完整走通后再上生产。2. 编译前的整体设计与组件选型2.1 为什么选源码编译而不是包管理安装系统自带的 OpenSSL 和 Nginx 包编译时用的都是默认参数默认参数里不包含国密算法套件。你就算把配置文件写上天底层没有对应的算法实现照样握手失败。包管理安装的另一个问题是版本锁定——你想换 OpenSSL 版本系统包管理器会告诉你依赖冲突动不了。源码编译的好处是彻底可控。OpenSSL 编译时可以指定启用国密算法相关的编译开关Nginx 编译时可以指定链接我们自己编译出来的 OpenSSL 路径。整条链路从底层到上层都是自己掌控的出了问题也好排查。代价就是编译过程比较长OpenSSL 全量编译大概十几分钟Nginx 快一些几分钟。但这是一次性的工作编译好之后可以打包分发到同架构的其他机器上不用每台都重新编。2.2 组件版本选择与兼容性考量版本选择这块我踩过不少坑直接说结论。OpenSSL 建议选 1.1.1 系列的一个较新小版本这个系列对国密算法的支持比较成熟社区补丁也全。太老的版本缺少必要的算法接口太新的 3.x 系列虽然也支持但部分国密相关的编译开关和 API 有变动和某些 Nginx 版本的配合需要额外打补丁新手容易卡住。Nginx 建议选 1.20 以上的稳定版。1.20 之后的版本对 TLS 1.3 和双证书配置的支持更完善而国密双栈恰恰需要双证书机制——一张国际算法证书、一张国密证书根据客户端支持的算法自动选择。还有一个容易被忽略的组件是国密算法引擎或补丁。OpenSSL 官方源码里对 SM2、SM3、SM4 的支持是有的但默认编译不一定全部启用。你需要确认编译配置里开启了对应的开关。有些场景还需要额外的国密引擎来提供硬件加速或特定实现这个看具体需求。组件推荐版本区间选择理由注意事项OpenSSL1.1.1 系列较新小版本国密支持成熟API 稳定避免过老版本缺接口Nginx1.20 以上稳定版双证书和 TLS 1.3 支持完善确认与 OpenSSL 兼容国密引擎按需硬件加速或特定实现非必需视场景而定2.3 双栈架构的核心思路双栈的意思是同一套 Nginx 同时支持国际算法和国密算法的 TLS 握手。实现方式不是跑两个 Nginx 实例而是在一个 server 块里配置两张证书Nginx 根据客户端 ClientHello 里支持的算法套件自动选择用哪张证书完成握手。这个机制的底层依赖是 OpenSSL 的多证书支持能力。当客户端发起连接时ClientHello 里会带上自己支持的密码套件列表。如果列表里有国密套件Nginx 就走国密证书和国密算法如果只有国际套件就走国际证书。整个过程对用户透明不需要手动切换端口或域名。这种设计的好处是迁移平滑。你不需要一次性把所有客户端都换成国密可以国际和国密并行跑一段时间逐步切换。对于面向公众的服务这一点尤其重要因为你不确定访问者用的是什么浏览器或客户端。3. 编译 OpenSSL 的关键参数与实操3.1 环境准备与依赖检查动手之前先把编译工具链装齐。不同发行版的包名不一样但核心就是编译器、make、perl 这几样。Perl 是必须的OpenSSL 的编译配置脚本是 Perl 写的没有它连 configure 都跑不起来。# 以常见发行版为例安装编译依赖 # 编译器与构建工具 gcc make perl # 如果系统里已有旧版 OpenSSL 开发包建议先确认路径 which openssl openssl version这里有个细节要注意系统里通常已经装了一个 OpenSSL编译新版本时不要覆盖系统自带的否则可能影响系统里其他依赖 OpenSSL 的服务。正确做法是把新版本编译安装到一个独立目录比如/usr/local/openssl-gm然后通过编译参数让 Nginx 去链接这个目录。提示编译前用ldd --version确认一下系统的基础库版本太老的系统可能需要先升级基础库否则编译过程中会出现奇怪的链接错误。3.2 configure 参数逐项拆解OpenSSL 的 configure 参数决定了最终产物包含哪些能力。国密支持相关的关键参数需要重点关注。./config \ --prefix/usr/local/openssl-gm \ --openssldir/usr/local/openssl-gm/ssl \ --shared \ enable-sm2 enable-sm3 enable-sm4 \ no-weak-ssl-ciphers逐项解释一下。--prefix指定安装目录独立目录避免污染系统。--openssldir指定配置文件目录。--shared生成动态链接库Nginx 链接时用动态库比静态库灵活后续升级 OpenSSL 不用重新编译 Nginx。enable-sm2 enable-sm3 enable-sm4这三个是国密算法的核心开关。SM2 是非对称算法用于证书和密钥交换SM3 是哈希算法用于摘要和签名SM4 是对称算法用于数据加密。三个都要开缺一个国密套件就跑不起来。no-weak-ssl-ciphers是安全加固选项禁用已知的弱加密套件。这个不是国密必需的但生产环境建议加上减少攻击面。参数写完之后执行 config 脚本它会检查环境并生成 Makefile。如果这一步报错通常是缺依赖或者参数拼写错误仔细看报错信息一般都能定位到具体原因。3.3 编译安装与验证configure 通过之后就是标准的编译安装流程。make -j$(nproc) make install-j$(nproc)是并行编译用满 CPU 核心数加快速度。OpenSSL 编译比较吃 CPU并行能省不少时间。编译过程中如果出现警告不用太紧张只要不是 error 就继续。但如果出现 error一定要停下来解决不要强行跳过。安装完成后验证一下国密算法是否可用。/usr/local/openssl-gm/bin/openssl version /usr/local/openssl-gm/bin/openssl ecparam -list_curves | grep SM2第一条命令看版本号确认是新编译的版本。第二条命令列出支持的椭圆曲线如果能看到 SM2说明国密非对称算法已经编译进去了。再验证一下 SM3 和 SM4。/usr/local/openssl-gm/bin/openssl dgst -sm3 /dev/null /usr/local/openssl-gm/bin/openssl enc -sm4-cbc -helpSM3 那条命令会对空输入做一次摘要能输出结果就说明算法可用。SM4 那条会打印帮助信息能看到 sm4-cbc 选项就说明对称算法也正常。注意验证时一定要用新编译的 openssl 完整路径不要直接敲openssl因为系统 PATH 里可能还是旧版本验证结果会误导你。4. 编译 Nginx 并链接国密 OpenSSL4.1 Nginx 编译参数配置Nginx 的编译配置比 OpenSSL 简单一些核心就是告诉它去哪里找我们刚编译好的 OpenSSL。./configure \ --prefix/usr/local/nginx-gm \ --with-http_ssl_module \ --with-http_v2_module \ --with-openssl/path/to/openssl-source \ --with-openssl-optenable-sm2 enable-sm3 enable-sm4 \ --with-cc-opt-I/usr/local/openssl-gm/include \ --with-ld-opt-L/usr/local/openssl-gm/lib -Wl,-rpath,/usr/local/openssl-gm/lib这里有几个关键点。--with-http_ssl_module是必须的没有它 Nginx 根本不支持 HTTPS。--with-http_v2_module建议加上HTTP/2 在现代服务里基本是标配。--with-openssl指向 OpenSSL 源码目录注意是源码目录不是安装目录。Nginx 编译时会自己再去编译一遍 OpenSSL所以需要源码。--with-openssl-opt把国密开关传给 OpenSSL 的编译过程确保 Nginx 链接的那个 OpenSSL 也带国密能力。--with-cc-opt和--with-ld-opt分别指定头文件路径和库文件路径。-Wl,-rpath这个参数很重要它把库路径写进可执行文件的运行时搜索路径这样 Nginx 启动时才能找到我们自定义目录下的 OpenSSL 库而不是系统默认的。4.2 编译过程中的常见报错处理编译 Nginx 时最常见的报错是找不到 OpenSSL 头文件或者链接不到库。如果报openssl/ssl.h: No such file or directory说明--with-cc-opt里的 include 路径不对检查一下/usr/local/openssl-gm/include下是不是真的有 ssl.h。如果报链接错误类似undefined reference to SSL_CTX_set_cipher_list通常是--with-ld-opt的库路径不对或者库文件没生成。回去检查 OpenSSL 编译安装那一步是否成功。还有一种情况是编译能过但 Nginx 启动时报error while loading shared libraries。这就是 rpath 没设对Nginx 运行时找不到动态库。解决办法要么重新编译加上正确的 rpath要么把库路径加到系统库配置里。# 临时验证可以用这个方式指定库路径 LD_LIBRARY_PATH/usr/local/openssl-gm/lib /usr/local/nginx-gm/sbin/nginx -t但生产环境不建议依赖环境变量还是编译时把 rpath 写死更可靠。4.3 验证 Nginx 的国密能力编译安装完成后先别急着配证书先确认 Nginx 链接的 OpenSSL 版本对不对。/usr/local/nginx-gm/sbin/nginx -V输出里会显示编译参数和 OpenSSL 版本。确认版本号和你编译的一致并且编译参数里能看到国密相关的选项。再进一步验证可以用 Nginx 自带的测试命令配合国密套件。/usr/local/openssl-gm/bin/openssl ciphers -v | grep -i sm这条命令列出所有包含 SM 的密码套件。如果能看到类似ECDHE-SM2-SM4-CBC-SM3这样的套件说明整条链路都通了。这个套件的含义是密钥交换用 SM2数据加密用 SM4-CBC完整性校验用 SM3。这是国密 TLS 的典型套件组合。5. 双栈证书配置与握手流程解析5.1 证书准备与格式要求国密证书和国际证书在格式上有区别。国际证书通常是 RSA 或 ECC 密钥国密证书用的是 SM2 密钥。生成国密证书需要用到支持 SM2 的工具链。# 生成 SM2 私钥 /usr/local/openssl-gm/bin/openssl ecparam -genkey -name SM2 -out sm2.key # 生成证书签名请求 /usr/local/openssl-gm/bin/openssl req -new -key sm2.key -out sm2.csr -sm3 # 自签名生成证书测试用生产环境应走正规 CA /usr/local/openssl-gm/bin/openssl x509 -req -in sm2.csr -signkey sm2.key -out sm2.crt -sm3 -days 365注意签名时指定-sm3表示用 SM3 做摘要。生产环境的国密证书需要向支持国密的 CA 机构申请自签名证书只适合测试。国际证书的生成流程不变用常规的 RSA 或 ECC 流程即可。两张证书准备好之后放到 Nginx 能读取的目录权限设置成只有 Nginx 工作进程用户可读。5.2 Nginx 双证书配置写法双栈配置的核心是在 server 块里同时指定两张证书。Nginx 从某个版本开始支持ssl_certificate指令出现多次分别对应不同的算法类型。server { listen 443 ssl; server_name example.local; # 国际算法证书 ssl_certificate /etc/nginx/certs/intl.crt; ssl_certificate_key /etc/nginx/certs/intl.key; # 国密算法证书 ssl_certificate /etc/nginx/certs/sm2.crt; ssl_certificate_key /etc/nginx/certs/sm2.key; # 密码套件配置同时包含国际和国密套件 ssl_ciphers ECDHE-SM2-SM4-CBC-SM3:ECDHE-SM2-SM4-GCM-SM3:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_protocols TLSv1.2 TLSv1.3; location / { root /usr/share/nginx/html; index index.html; } }ssl_ciphers里把国密套件放在前面国际套件放在后面。ssl_prefer_server_ciphers on表示优先使用服务端指定的顺序。这样配置之后支持国密的客户端会优先协商到国密套件不支持的客户端会自动落到国际套件。提示国密套件的名称在不同 OpenSSL 版本里可能有细微差异配置前先用openssl ciphers -v确认实际支持的套件名称直接复制命令输出里的名称最稳妥。5.3 握手过程与算法协商机制客户端发起连接时ClientHello 里带着自己支持的密码套件列表。Nginx 收到后结合服务端配置的套件列表选出一个双方都支持的最优套件。如果客户端支持国密套件且服务端配置了国密证书协商结果就是国密套件后续的密钥交换、数据加密、摘要校验全部走国密算法。如果客户端不支持国密协商结果就是国际套件走常规的 RSA 或 ECC 流程。这个协商过程完全由 TLS 协议层自动完成应用层无感知。你可以用抓包工具看握手过程ClientHello 和 ServerHello 里的 CipherSuite 字段能清楚看到最终选了什么套件。有一个细节值得注意国密 TLS 的握手流程和国际 TLS 略有不同。国密标准里定义了双证书体系签名证书和加密证书是分开的。但在实际部署中很多场景用的是单证书简化模式。具体用哪种模式取决于你的客户端要求和服务端实现。这一点在配置前要和客户端侧确认清楚否则可能出现服务端配好了但客户端不认的情况。6. 常见问题排查与性能调优实录6.1 握手失败的典型原因与定位方法握手失败是国密改造里最常见的问题表现是客户端连不上或者连接被重置。排查的第一步是看 Nginx 错误日志。tail -f /usr/local/nginx-gm/logs/error.log如果日志里出现no shared cipher说明客户端和服务端没有共同支持的密码套件。原因可能是服务端没配国密套件或者客户端不支持国密或者套件名称写错了。如果出现certificate verify failed说明证书验证没过。可能是证书链不完整或者客户端不信任服务端的 CA。国密证书的信任链和国际证书是两套体系客户端需要预置对应的根证书。如果出现unsupported protocol检查ssl_protocols配置确认协议版本双方都支持。用 openssl 命令行工具可以模拟握手快速定位问题。# 模拟国密握手 /usr/local/openssl-gm/bin/openssl s_client -connect example.local:443 -cipher ECDHE-SM2-SM4-CBC-SM3 # 模拟国际握手 openssl s_client -connect example.local:443第一条命令如果握手成功会打印出证书信息和协商的套件。失败的话会给出具体错误根据错误信息对症下药。6.2 性能影响评估与优化建议国密算法的性能和国际算法有差异。SM2 在密钥交换阶段的计算量比 RSA 小但比 ECC 略大。SM4 的加解密速度和 AES 接近在支持硬件加速的平台上差距更小。SM3 的哈希速度和 SHA256 相当。整体来看国密 TLS 的性能损耗在可接受范围内但有几个优化点值得注意。第一开启会话复用。TLS 握手是性能开销的大头会话复用能避免重复的完整握手。配置ssl_session_cache和ssl_session_timeout即可。ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m;第二如果平台支持国密硬件加速编译 OpenSSL 时启用对应的引擎。硬件加速能把 SM2 和 SM4 的计算卸载到专用硬件性能提升明显。但硬件加速依赖特定平台通用服务器上不一定有。第三合理设置ssl_buffer_size。默认值对大多数场景够用但如果传输大量小文件适当调小可以减少延迟。优化项配置指令预期效果适用场景会话复用ssl_session_cache减少完整握手次数所有场景硬件加速编译时启用引擎提升国密计算速度支持加速的平台缓冲区调优ssl_buffer_size降低小文件延迟小文件传输多6.3 独家避坑经验汇总说几个我在实际操作中踩过的坑都是文档里不会写的。第一个坑是 OpenSSL 编译时的enable-sm2开关。有些小版本的 OpenSSL 里SM2 的支持是默认开启的不需要显式指定。但如果你显式指定了而该版本又不认这个参数configure 会直接报错。解决办法是先跑一次./config --help看看支持哪些参数按实际支持的来写。第二个坑是 Nginx 的--with-openssl参数。这个参数指向的是 OpenSSL 源码目录Nginx 编译时会自己再编译一遍 OpenSSL。但如果你之前已经单独编译安装过 OpenSSLNginx 编译时可能会混淆用了错误的版本。稳妥的做法是 Nginx 编译时用一个干净的 OpenSSL 源码副本不要和之前编译过的目录混用。第三个坑是证书链的顺序。国密证书的证书链里根证书和中间证书的顺序有讲究。顺序错了某些客户端会验证失败。一般来说服务端证书在前中间证书在后根证书通常不需要放在服务端。具体顺序参考 CA 机构提供的说明。第四个坑是双栈配置下的套件优先级。如果你把国际套件放在国密套件前面支持国密的客户端也会优先协商到国际套件国密能力就浪费了。所以国密套件一定要放在前面配合ssl_prefer_server_ciphers on使用。第五个坑是日志级别。排查握手问题时把 Nginx 日志级别调到info或debug能看到详细的握手过程。但生产环境不要长期开 debug日志量太大影响性能。排查完记得调回去。error_log /usr/local/nginx-gm/logs/error.log info;这个配置在排查阶段很有用能看到每一步握手协商的细节。调完之后改回error或warn级别。7. 编译产物的打包与迁移部署7.1 制作可分发的编译产物编译一次不容易如果有多台同架构的机器要部署没必要每台都重新编译。把编译产物打包直接分发过去就行。需要打包的内容包括OpenSSL 安装目录、Nginx 安装目录、以及必要的配置文件模板。打包前先确认目标机器的架构和基础库版本一致否则动态库可能不兼容。# 打包 OpenSSL 和 Nginx 安装目录 tar czf nginx-gm-package.tar.gz /usr/local/openssl-gm /usr/local/nginx-gm分发到目标机器后解压到相同路径然后验证动态库依赖是否满足。ldd /usr/local/nginx-gm/sbin/nginx | grep not found如果有not found的库说明目标机器缺依赖需要先补上。常见的是 libpcre、zlib 这些基础库。7.2 迁移后的配置调整与验证迁移到新机器后证书路径、日志路径、用户权限这些可能需要调整。检查 Nginx 配置文件里的路径是否和新环境一致。启动前先用nginx -t测试配置语法。/usr/local/nginx-gm/sbin/nginx -t测试通过后再启动。启动后立即验证国密握手是否正常用前面提到的 openssl s_client 命令测一遍。确认无误后再把流量切过来。注意迁移部署时最容易忽略的是文件权限。Nginx 工作进程通常以非 root 用户运行如果证书文件权限设置过严工作进程读不到证书握手会失败。确保证书文件对 Nginx 工作进程用户可读。7.3 版本升级与回滚策略国密组件后续可能需要升级比如 OpenSSL 出了安全补丁或者 Nginx 有新版本。升级策略建议是先在测试环境验证确认没问题再上生产。回滚方案也要提前准备好。最简单的回滚就是保留旧版本的安装目录出问题时把软链接或启动脚本指回旧版本。所以升级时不要直接覆盖旧目录而是新装一个目录通过切换启动路径来实现版本切换。# 假设旧版本在 /usr/local/nginx-gm-old # 新版本在 /usr/local/nginx-gm-new # 通过软链接切换 ln -sfn /usr/local/nginx-gm-new /usr/local/nginx-gm这样切换和回滚都只是一条命令的事风险可控。我个人在实际操作中的体会是国密改造这件事编译环节虽然繁琐但恰恰是最值得投入时间的地方。把编译参数吃透把依赖关系理顺后面配置和运维阶段会省很多事。反过来如果编译阶段图省事用了来路不明的二进制包后面出问题排查起来会非常痛苦因为你根本不知道那个包是用什么参数编出来的。所以宁可多花半小时自己编译也不要在这件事上走捷径。

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

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

免费获取报价 →
↑