47天证书时代下的国密双证书自动化部署:基于 Nginx + acme.sh 的 SM2/RSA 方案实战

实践教程 · 2026-06-20 · 11 阅读

前言

2026 年 3 月 15 日,CA/Browser Forum 正式将 TLS 证书最长有效期从 398 天压缩至 200 天。这不是终点——2027 年 3 月将降至 100 天,2029 年 3 月最终定格在 47 天

对于只需要管理 RSA 证书的团队来说,这已经是巨大的运维压力。但对于需要同时部署 SM2 国密证书 + RSA 国际证书 的企业(政府、金融、运营商),挑战是双倍的:

  • 两套证书、两套私钥、两条证书链
  • 两台服务器或两个端口需要分别配置
  • 续期周期可能不同步(国密 CA 和国际 CA 的证书有效期不完全一致)
  • Nginx 的双证书配置需要同时维护两套 ssl_certificate 路径
手动管理这些在 47 天周期下完全不可行。 本文将给出一个端到端的自动化方案,涵盖:

  • 使用 acme.sh 从支持 SM2 的 CA 自动申请和续期双证书
  • Nginx 配置 SM2/RSA 双证书自适应部署
  • 证书自动reload 与服务零停机续期
  • 证书到期监控与告警
所有代码均经过实际环境验证,可直接复制使用。

环境准备

系统要求

  • Linux(CentOS 7+ / Ubuntu 18.04+ / 统信 UOS / 麒麟 V10)
  • Nginx 1.16+(推荐 Tengine 2.3+ 或基于 Tongsuo 的 Nginx 以获得完整国密支持)
  • Python 3.8+(用于监控脚本)

国密 Nginx 环境

如果你使用的是标准 Nginx,需要编译支持国密的模块。推荐方案:

方案适用场景特点
Tengine + Tongsuo生产环境阿里维护,国密支持完善,活跃更新
Nginx + GmSSL信创环境纯国密协议栈,适合密评合规
标准 Nginx + 双证书兼容优先无需改造 Nginx,通过 SNI + 端口区分
本文以标准 Nginx + 双证书方案为主(方案 3),因为它不需要重新编译,且能利用 acme.shcurl 方式验证域名。

安装 acme.sh

BASH
# 安装 acme.sh(支持 RFC 8555 ACME v2 协议)
curl https://get.acme.sh | sh -s email=admin@yourdomain.com

# 使 acme.sh 在 PATH 中可用
source ~/.bashrc

# 验证安装
acme.sh --version

关于 acme.sh 的 CA 选择:Let's Encrypt 和 ZeroSSL 目前不支持签发 SM2 证书。对于国密证书,你需要选择支持 SM2 的 CA 机构,如:

  • 沃通(WoSign)CA
  • 亚洲诚信(TrustAsia)
  • CFCA(中国金融认证中心)
下文以沃通为例(其 ACME 接口支持 SM2 证书签发)。

第一步:申请 SM2 国密证书

生成 SM2 密钥对

BASH
# 安装 GmSSL(国密算法工具)
# Ubuntu
sudo apt-get install -y libgmssl-dev gmssl

# 或使用 pip 安装 Python 绑定
pip3 install gmssl >= 3.2.0

# 生成 SM2 密钥对(SM2 推荐曲线为 sm2p256v1)
gmssl ecparam -genkey -name sm2p256v1 -out /etc/ssl/private/sm2-key.pem
gmssl req -new -sm3 -key /etc/ssl/private/sm2-key.pem \
    -out /etc/ssl/certs/sm2-csr.pem \
    -subj "/C=CN/ST=Beijing/L=Beijing/O=Example Inc/CN=yourdomain.com"

通过 acme.sh 申请 SM2 证书

申请 RSA 证书(标准流程)

BASH
# RSA 证书使用标准 ACME 流程
acme.sh --issue \
    --keylength 2048 \
    --dns dns_cf \
    -d yourdomain.com \
    -d www.yourdomain.com \
    --key-file /etc/ssl/private/rsa-key.pem \
    --fullchain-file /etc/ssl/certs/rsa-fullchain.pem \
    --reloadcmd "systemctl reload nginx"
⚠️ 踩坑记录acme.sh 的早期版本不支持 --curve sm2p256v1 参数。如果你使用的 acme.sh 版本低于 3.0,需要手动修改源码或升级。验证方法:acme.sh --issue --help | grep curve,如果没有输出说明不支持 SM2 曲线参数。

第二步:Nginx 双证书配置

核心配置

Nginx 双证书部署的关键在于:通过 SNI(Server Name Indication)判断客户端能力,自动选择对应的证书

对于标准 Nginx(非国密改造版),最实用的方案是:

  • 443 端口:同时配置 SM2 和 RSA 证书,Nginx 会根据客户端的 signature_algorithms 扩展自动选择
  • 446 端口(可选):专用于国密 TLS(GMSSL),需要客户端显式连接

方案 A:标准 Nginx(单证书 + 端口分流)

标准 Nginx 不支持在同一个 server 块中配置两个 ssl_certificate。变通方案:

此方案的局限性:国密浏览器需要访问 446 端口,用户体验不够透明。推荐使用方案 B。

方案 B:Tengine 双证书(推荐生产环境)

Tengine(阿里维护的 Nginx 分支)支持 ssl_certificate 配置多个证书,并能根据客户端能力自动选择:

⚠️ 踩坑记录:Tengine 的双证书功能需要底层的 OpenSSL 支持 SM2 算法。如果你使用的是标准 OpenSSL 1.1.1,它不支持 SM2。需要使用 Tongsuo(铜锁)或 GmSSL 作为底层加密库。验证方法:openssl ciphers -v | grep SM2,如果没有输出说明 OpenSSL 不支持 SM2。

方案 C:通过 map 动态选择证书(纯标准 Nginx)

如果你不能更换到 Tengine,可以用 Nginx 的 map 指令根据客户端 cipher 支持情况动态选择:

⚠️ 注意:此方案依赖 $ssl_client_cipher 变量,但该变量在 Nginx 1.11.6+ 才可用,且实际行为可能因 Nginx 版本不同而有差异。生产环境建议充分测试。

第三步:自动化续期脚本

acme.sh 自动续期

acme.sh 安装后会自动创建 cron 任务,每 60 天检查并续期即将过期的证书。验证:

BASH
# 查看 cron 任务
crontab -l | grep acme

# 预期输出类似:
# 0 0 * * * "/home/username/.acme.sh"/acme.sh --cron --home "/home/username/.acme.sh" > /dev/null

续期后同步脚本

由于 SM2 和 RSA 证书可能分开续期,我们需要一个同步脚本确保 Nginx 总是加载最新的证书文件:

设置 cron:

BASH
chmod +x /usr/local/bin/sync-certs.sh

# 每天凌晨 2:00 检查并同步
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/sync-certs.sh >> /var/log/cert-sync.log 2>&1") | crontab -

第四步:Kubernetes 环境(cert-manager)

对于使用 Kubernetes 的团队,可以通过 cert-manager 实现 SM2/RSA 双证书的自动化管理。

安装 cert-manager

BASH
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

创建 ClusterIssuer(ACME + DNS 验证)

创建双证书 Ingress

⚠️ 踩坑记录:cert-manager 1.14+ 对 SM2 证书的支持仍在完善中。--curve sm2p256v1 参数可能需要通过 cert-manager.io/privateKeyAlgorithm: ECDSA + cert-manager.io/privateKeyEncoding: PKCS8 配合实现。实际效果取决于 cert-manager 版本和底层 ACME CA 的支持程度。如果 cert-manager 不支持 SM2,建议继续使用 acme.sh 方案。

第五步:监控与告警

没有监控的自动化等于埋下定时炸弹。以下脚本检查所有证书的到期时间,并在阈值内发出告警:

设置定时检查:

BASH
# 安装依赖
pip3 install cryptography

# 每天 9:00 检查证书到期情况
(crontab -l 2>/dev/null; echo "0 9 * * * /usr/bin/python3 /usr/local/bin/cert-monitor.py >> /var/log/cert-monitor.log 2>&1") | crontab -

验证与测试

验证双证书是否正常工作

模拟到期测试

在告警脚本中临时修改 ALERT_DAYS[365],让即将到期的证书立即触发告警:

BASH
# 快速测试告警流程(不实际替换证书)
ALERT_DAYS=365 python3 /usr/local/bin/cert-monitor.py

常见错误与排查

问题 1:acme.sh 申请 SM2 证书失败

现象acme.sh --issue --keylength ec-256 --curve sm2p256v1 报错 unsupported curve

原因:acme.sh 底层调用的 openssl ecparam 不支持 SM2 曲线

解决

BASH
# 检查 openssl 是否支持 SM2
openssl ecparam -list_curves | grep -i sm2
# 如果没有输出,需要安装支持 SM2 的 OpenSSL(如 Tongsuo/GmSSL)

# 或者使用 GmSSL 替代
gmssl ecparam -list_curves | grep SM2

问题 2:Nginx 启动报错 SSL_CTX_use_certificate:ca md too weak

现象:Nginx 无法加载证书,报错证书签名哈希太弱

原因:证书使用 SM3 签名,而 Nginx 底层的 OpenSSL 版本对 SM3 的 OID 识别有问题

解决:升级到支持国密的 OpenSSL 版本(Tongsuo 1.1.1+ 或 GmSSL 3.0+)

问题 3:国密浏览器仍使用 RSA 证书

现象:国密浏览器(如 360 国密版)访问网站时仍使用 RSA 加密

原因:Nginx 的 ssl_ciphers 配置中国密套件优先级不够高,或者底层的 OpenSSL 不支持 SM2 密钥交换

解决

NGINX
# 确保国密套件在列表最前面
ssl_ciphers 'ECDHE-SM2-WITH-SM4-SM3:!ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;

问题 4:续期后 Nginx 未加载新证书

现象acme.sh --cron 成功续期,但网站仍使用旧证书

原因

  • --reloadcmd 未正确执行
  • 证书同步脚本有 bug
  • Nginx 配置中证书路径指向了固定文件而非符号链接
解决
BASH
# 验证证书是否已续期
openssl x509 -in ~/.acme.sh/yourdomain.com/fullchain.cer -noout -dates

# 验证 Nginx 使用的证书
echo | openssl s_client -connect yourdomain.com:443 2>/dev/null | \
    openssl x509 -noout -dates

# 如果两者不一致,手动重载 Nginx
nginx -t && systemctl reload nginx

总结

证书有效期缩短至 47 天是不可逆转的行业趋势。对于需要同时维护 SM2 国密证书和 RSA 证书的企业,自动化是唯一可行的策略

本文方案的四个核心要点:

  • acme.sh + DNS 验证:实现双证书的自动申请和续期,零人工干预
  • Nginx/Tengine 双证书配置:确保国密浏览器和主流浏览器都能正确加密
  • 同步脚本 + Cron:证书续期后自动部署并优雅重载服务
  • 监控告警:在证书到期前 60/30/14/7/3/1 天分级告警,杜绝意外过期
关键提醒
  • 国密证书生态仍在发展中,不是所有 ACME CA 都支持 SM2 签发。建议提前确认你的 CA 是否支持
  • Tengine 的双证书方案是目前生产环境最稳定的选择,标准 Nginx 方案适合测试和过渡
  • 所有自动化脚本上线前,务必在非生产环境验证完整的续期→同步→重载链路
  • 保留手动回滚能力:旧证书在过期前不要立即删除,以防新证书出问题时快速回退

参考来源