本文面向发布到 CSDN,汇总本人在 Windows + WSL2 编译、Docker 部署、CentOS 生产环境跑通 Nginx 国密 HTTPS(TLCP) 时使用的源码版本 、目录布局 ,以及为调通而做的全部修改(含配置、脚本、证书处理;不含对 Nginx 核心 C 源码的 patch)。
参考博文:基于 GmSSL 搭建 Nginx 国密反代(流程可参考,但部分写法如
ssl_protocols GMTLS与当前 OCL/Nginx 行为不完全一致,下文会说明)。
一、环境与目录布局
| 环境 | 说明 |
|---|---|
| 开发机 | Windows 10/11 + WSL2 Ubuntu 24.04 |
| 生产机 | CentOS 7 系(10.131.111.250),Docker 运行 |
| 源码根目录 | /mnt/d/codes/(Windows 对应 D:\codes\) |
推荐三仓库并列:
text
/mnt/d/codes/
├── GmSSL/ # 国密算法库
├── OpenSSL-Compatibility-Layer/ # OpenSSL 兼容层(OCL)
└── nginx/ # Nginx 1.25.3 源码 + Docker 工程
证书调试目录(与仓库分离,便于拷到生产机):
text
/mnt/d/nginx_gm/certs/ # 自建 CA + 服务器 TLCP 证书
/opt/nginx/certs/ # 生产机挂载进容器
/opt/nginx/conf/nginx.conf # 生产机自定义主配置
二、引用的三个项目及版本(务必对齐)
2.1 版本总表
| 项目 | 仓库 | 推荐 Tag / 版本 | 用途 |
|---|---|---|---|
| GmSSL | https://github.com/guanzhi/GmSSL | v3.1.1 | OCL 依赖;gmssl 命令行生成证书 |
| OpenSSL-Compatibility-Layer | https://github.com/GmSSL/OpenSSL-Compatibility-Layer | v0.8.1 | 提供 libssl.so / libcrypto.so,伪装 OpenSSL API |
| Nginx | https://nginx.org 或本仓库 release-1.25.3 |
1.25.3 | OCL README 明确测试过的版本 |
| 运行环境 | 版本 |
|---|---|
| WSL 编译 | Ubuntu 24.04 ,gcc 13.3.0 |
| Docker 基础镜像 | Ubuntu 24.04 |
| Docker 镜像标签 | nginx-gmssl:1.25.3 |
2.2 版本不对齐时的典型现象
| 错配 | 现象 |
|---|---|
| GmSSL 用 master,Nginx/OCL 用 v3.1.1 编译 | WSL 上 gmssl tlcp_client 报 tls.c:2829:tls_init(): error(见下文 §5.3) |
| Nginx 1.31+ 未在 OCL 列表中 | 编译或握手阶段未知问题,不建议生产首选用 |
| 系统 OpenSSL 3.x 未被子替换 | nginx -V 显示 SSL 但握手仍是国际算法 |
gmssl 与链接进 Nginx 的 libgmssl 不是同一次编译 |
证书工具能生成,运行时库行为不一致 |
2.3 编译安装顺序(固定)
text
① GmSSL (v3.1.1) → sudo make install → /usr/local/lib/libgmssl.so
② OCL (v0.8.1) → sudo make install → /usr/local/lib/libssl.so, libcrypto.so
③ Nginx (1.25.3) → ./configure && make → objs/nginx
安装 OCL 后必须确认默认头文件/库来自 OCL,而不是系统 OpenSSL:
bash
grep -i OPENSSL /usr/local/include/openssl/opensslv.h
ldd /usr/local/lib/libcrypto.so | grep gmssl
三、各组件编译参数(最终调通版)
3.1 GmSSL v3.1.1
bash
cd /mnt/d/codes/GmSSL
git fetch --tags
git checkout v3.1.1
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
# 命令行工具
which gmssl # 期望 /usr/local/bin/gmssl
说明 :证书在 /mnt/d/nginx_gm/certs 用 gmssl sm2keygen / certgen / reqgen / reqsign 生成;口令示例 Ilove@cn123,签名私钥与加密私钥必须相同(OCL 硬性要求)。
对 GmSSL 源码的修改 :无 (仅 git checkout v3.1.1,避免使用 master 做客户端测试)。
3.2 OpenSSL-Compatibility-Layer v0.8.1
bash
cd /mnt/d/codes/OpenSSL-Compatibility-Layer
git fetch --tags
git checkout v0.8.1
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make install
安装结果:
- 头文件:
/usr/local/include/openssl/(OCL 头文件) - 库:
/usr/local/lib/libssl.so、libcrypto.so(底层依赖libgmssl)
对 OCL 源码的修改(生产调通路径) :无 patch 。
理解其行为即可,不必改代码(见 §5.1 pem.c 逻辑)。
社区可选 patch(浏览器访问报错时) :
OCL Issue #13 提到在 GmSSL 的 src/tlcp.c 的 tlcp_do_accept 中注释客户端扩展校验,以兼容部分国密浏览器。本人生产调通未改此文件;若奇安信/360 握手失败可再评估。
3.3 Nginx 1.25.3
第一次 configure(踩坑,缺 PCRE)
bash
./configure \
--prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-stream \
--with-stream_ssl_module \
--without-http_rewrite_module
objs/ngx_auto_config.h 中无 NGX_PCRE,配置里写 location ~* \.(js|css|...)$ 会报:
text
nginx: [emerg] using regex "..." requires PCRE library
最终 configure(调通版)
bash
sudo apt install -y libpcre3-dev
./configure \
--prefix=/usr/local/nginx \
--with-http_ssl_module \
--with-stream \
--with-stream_ssl_module \
--with-pcre \
--with-pcre-jit
make -j$(nproc)
当前工程内记录的编译参数(objs/ngx_auto_config.h):
c
#define NGX_CONFIGURE " --prefix=/usr/local/nginx --with-http_ssl_module --with-stream --with-stream_ssl_module --without-http_rewrite_module --with-pcre --with-pcre-jit"
验证:
bash
objs/nginx -V
# 应含 openssl 相关信息,且 ldd objs/nginx | grep -E 'ssl|crypto|gmssl'
对 Nginx 官方源码 src/ 的修改 :无 。国密能力完全来自链接 OCL 的 libssl,不是 nginx 补丁模块。
四、Docker 工程修改(本仓库 nginx/docker/)
采用「WSL 编好二进制 + 拷贝 .so → 镜像只打包」策略,不在镜像内重编三件套。
4.1 docker/prepare.sh(打包动态库)
将 sibling 目录编译产物拷入 vendor/lib:
text
../GmSSL/build/bin/libgmssl.so*
../OpenSSL-Compatibility-Layer/build/libssl.so*
../OpenSSL-Compatibility-Layer/build/libcrypto.so*
4.2 Dockerfile 要点
- 基础镜像:
ubuntu:24.04 - 安装运行时依赖:
libpcre3、zlib1g(不在镜像里装开发包) COPY objs/nginx→/usr/local/nginx/sbin/nginxENV LD_LIBRARY_PATH=/usr/local/libEXPOSE 80 4443(生产映射-p 443:443时由宿主机nginx.conf决定 listen)
4.3 docker-entrypoint.sh 修改(重要)
修改前(旧镜像逻辑,导致容器秒退):
sh
if [ ! -f /certs/tlcp_server_certs.pem ] || [ ! -f /certs/tlcp_server_keys.pem ]; then
echo "error: TLCP certs required under /certs (see docker/README.md)"
exit 1
fi
生产挂载为 /etc/nginx/certs/,文件名也可能是 sign-fullchain.pem 等,与 /certs/tlcp_server_*.pem 不一致 → entrypoint 直接 exit 1。
修改后(当前文件):
sh
#!/bin/sh
set -e
NGINX=/usr/local/nginx/sbin/nginx
ldconfig 2>/dev/null || true
if [ "$1" = "$NGINX" ] || [ "$(basename "$1" 2>/dev/null)" = "nginx" ]; then
"$NGINX" -t
fi
exec "$@"
仅做 nginx -t,不再硬编码证书路径。
4.4 docker/default.conf 修改
修改前:
nginx
ssl_certificate /certs/tlcp_server_certs.pem;
ssl_certificate_key /certs/tlcp_server_keys.pem;
ssl_password_file /certs/tlcp_server_password.txt;
修改后:
nginx
ssl_certificate /etc/nginx/certs/tlcp_server_certs.pem;
ssl_certificate_key /etc/nginx/certs/tlcp_server_keys.pem;
ssl_password_file /etc/nginx/certs/tlcp_server_password.txt;
与 docker-compose.yml 挂载 ./certs:/etc/nginx/certs:ro 一致。
4.5 生产 docker run(相对最初命令的修正)
bash
docker run -d \
--name nginx-gm \
-p 443:443 \
-v /opt/nginx/certs:/etc/nginx/certs:ro \
-v /opt/nginx/conf/nginx.conf:/usr/local/nginx/conf/nginx.conf:ro \
-v /opt/nginx/html:/etc/nginx/html:ro \
-v /opt/nginx/logs:/usr/local/nginx/logs \
nginx-gmssl:1.25.3
| 最初写法 | 问题 |
|---|---|
--name nginx-gm 重复 |
命令非法或覆盖 |
logs → /var/log/nginx:ro |
前缀应为 /usr/local/nginx/logs,且不能只读 |
cache 无关只读挂载 |
与 nginx 无关,易误导 |
| 旧 entrypoint + 旧镜像 | 证书未放到 /certs/tlcp_server_*.pem 即退出 |
五、生产 nginx.conf 与证书(运维侧修改)
5.1 证书文件(OCL 要求,非 Nginx 官方格式)
服务器侧三个文件(/opt/nginx/certs/):
| 文件 | 内容顺序 |
|---|---|
tlcp_server_certs.pem |
终端签名证 → 终端加密证 → 中间 CA(不要根证) |
tlcp_server_keys.pem |
签名加密私钥 → 加密加密私钥 (两段 ENCRYPTED PRIVATE KEY) |
password.txt |
一行口令,无 \r |
生成示例(与 OCL README 一致):
bash
cat signcert.pem enccert.pem cacert.pem > tlcp_server_certs.pem
cat signkey.pem enckey.pem > tlcp_server_keys.pem
printf '%s\n' 'Ilove@cn123' > password.txt
证书处理上的"修改"(非改 C 代码):
- 厂商未加密私钥 (
BEGIN PRIVATE KEY)→ 用gmssl pkcs8 -topk8加密后再合并。 - 厂商分体 fullchain (
sign-fullchain.pem+enc-fullchain.pem)→ 改为 OCL 推荐的单证链 + 双钥文件。 password.txt与密钥口令不一致 (曾用12345678而密钥为Ilove@cn123)→ 统一口令并cat -A检查无^M。
5.2 生产 server 块(最终)
nginx
server {
listen 443 ssl;
server_name 10.131.111.250;
ssl_certificate /etc/nginx/certs/tlcp_server_certs.pem;
ssl_certificate_key /etc/nginx/certs/tlcp_server_keys.pem;
ssl_password_file /etc/nginx/certs/password.txt;
ssl_ecdh_curve sm2p256v1;
ssl_ciphers ECDHE-SM2-WITH-SMS4-SM3:ECDHE-SM2-WITH-SMS4-GCM-SM3;
ssl_prefer_server_ciphers on;
location / {
root /etc/nginx/html;
index index.html index.htm;
}
}
相对 CSDN 参考文/初稿删除或禁止的项:
nginx
# 错误 --- Nginx 配置解析不认 GMTLS
ssl_protocols GMTLS TLSv1.2;
# 错误 --- OCL 双钥加密场景不要删
# ssl_password_file ...
说明 :TLCP 由 GmSSL 在 libssl 内完成,不需要 也不能在 ssl_protocols 里写 GMTLS。
5.3 OCL 读私钥逻辑(理解即可,解释为何必须加密+口令)
OCL src/pem.c 中 PEM_read_bio_PrivateKey 核心逻辑(v0.8.1 / main 同类):
c
// 必须有 password 回调
if (!bio || !cb || !u) { error_print(); return NULL; }
cb(pass, sizeof(pass), 0, u);
// 同一 BIO 连续读两把 SM2 私钥
sm2_private_key_info_decrypt_from_pem(&pkey->signkey, pass, bio);
sm2_private_key_info_decrypt_from_pem(&pkey->kenckey, pass, bio);
因此:
- 必须配置
ssl_password_file; tlcp_server_keys.pem必须是 PKCS#8 加密 格式(BEGIN ENCRYPTED PRIVATE KEY);- 两把钥同一口令。
六、GmSSL 命令行版本问题(WSL 调试)
6.1 现象
bash
gmssl tlcp_client -get / -host 10.131.111.250 -port 443 -cacert rootcacert.pem
# /mnt/d/codes/GmSSL/src/tls.c:2829:tls_init():
# tlcp_client: error
此时服务器 docker exec nginx-gm nginx -t 已 successful ,nc 443 通。
6.2 原因
WSL 中 /usr/local/bin/gmssl 若来自 GmSSL master ,tls_init() 会检查 key_exchange_modes != 0,而 tlcp_client 未设置 supported_groups / signature_algorithms,导致客户端在握手前就失败。
v3.1.1 的 tls_init 无此检查,且会正确设置 conn->is_client = ctx->is_client。
6.3 处理(版本切换,非改 tls.c)
bash
cd /mnt/d/codes/GmSSL
git checkout v3.1.1
rm -rf build && mkdir build && cd build && cmake .. && make -j$(nproc)
sudo make install
6.4 不要用这些方式判断服务是否存活
| 工具 | 结果 | 原因 |
|---|---|---|
curl https://IP:443 |
PR_END_OF_FILE_ERROR |
国际 TLS vs TLCP |
openssl pkey -in tlcp_server_keys.pem |
unable to load key |
系统 OpenSSL 不支持 SM2 |
七、修改项汇总表(便于 CSDN 读者对照)
| 序号 | 对象 | 是否改上游 C 源码 | 修改内容 |
|---|---|---|---|
| 1 | GmSSL | 否 | 固定 tag v3.1.1 ,不用 master 跑 tlcp_client |
| 2 | OCL | 否(可选 tlcp.c 见 Issue #13) | 固定 tag v0.8.1 ;理解 pem.c 双钥+解密 |
| 3 | Nginx | 否 | configure 增加 --with-pcre --with-pcre-jit |
| 4 | docker-entrypoint.sh |
项目脚本 | 去掉 /certs/tlcp_server_*.pem 强校验,仅 nginx -t |
| 5 | docker/default.conf |
项目配置 | 证书路径 /certs → /etc/nginx/certs |
| 6 | 生产 nginx.conf |
运维配置 | listen 443;删 GMTLS;OCL 三件套路径 |
| 7 | 证书文件 | 运维 | 合并证链/双钥;加密私钥;password.txt 去 CRLF |
| 8 | docker run |
运维 | 日志可写、去掉错误 volume、更新镜像 |
八、验证通过标准(本环境)
bash
# 1. 编译链
ldd /usr/local/nginx/sbin/nginx | grep gmssl
objs/nginx -V
# 2. 容器
docker exec nginx-gm /usr/local/nginx/sbin/nginx -t
# configuration file ... test is successful
# 3. 监听(镜像可能无 ss)
docker exec nginx-gm sh -c 'cat /proc/net/tcp' | grep 01BB # 443
# 4. 客户端(GmSSL v3.1.1)
gmssl tlcp_client -get / -host 10.131.111.250 -port 443 -cacert rootcacert.pem
# 5. 浏览器
# 国密浏览器 + 导入 rootcacert.pem → https://10.131.111.250/
九、发布 CSDN 时的建议标签
Nginx 国密 GmSSL TLCP OpenSSL-Compatibility-Layer SM2 Docker HTTPS
十、参考链接
- GmSSL/OpenSSL-Compatibility-Layer README
- guanzhi/GmSSL(Tag v3.1.1)
- GmSSL Issue #1156 - invalid value GMTLS
- OCL Issue #13 - 浏览器访问国密 nginx
- 本人部署踩坑合集:同目录
nginx-gmssl-部署总结.md
版权声明 :版本号与修改记录基于 2026 年 5 月实际调试环境整理;若上游 tag 更新,请以各项目 Release 说明为准,并重新做 nginx -t 与 tlcp_client 回归。