分层证书体系完整方案
本方案使用 openssl 生成根CA和中间CA,然后使用 CFSSL(基于中间CA)作为API服务,签发所有终端证书(服务端和客户端)。
终端证书有效期设为 1年,通过设备端自动续期实现无需人工干预。
适用场景:物联网百万级设备 mTLS 接入,Nginx 做 SSL 终结,EMQX 作为 MQTT Broker。
一、环境准备
本次演示的各个环境
操作系统:Ubuntu 20.04 64位
OpenSSL:1.1.1f(Ubuntu 20.04 默认版本)
Nginx:1.28.0(Ubuntu)
CFSSL:1.6.5
EMQX:5.8 开源社区版
1.1 安装必要工具
# 安装 openssl(通常已预装)
sudo apt update && sudo apt install openssl -y # Debian/Ubuntu
# sudo yum install openssl -y # CentOS/RHEL
# 安装 CFSSL(v1.6.5)
curl -sLo cfssl "https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssl_1.6.5_linux_amd64"
curl -sLo cfssljson "https://github.com/cloudflare/cfssl/releases/download/v1.6.5/cfssljson_1.6.5_linux_amd64"
chmod +x cfssl cfssljson
sudo mv cfssl cfssljson /usr/local/bin/
cfssl version # 验证1.2 创建工作目录
mkdir -p ~/pki/{root-ca,intermediate-ca,server,client,cfssl}
cd ~/pki ┌─────────────────────────────────────┐
│ 根证书 (Root CA) │
│ root-ca.crt(导入设备) │
│ 有效期: 20年 (2046年) │
│ 角色: 信任锚,离线保存 │
│ 私钥: root-ca.key (绝密, 保险柜) │
└──────────────┬──────────────────────┘
│
│ 签发
│
┌──────────────▼──────────────────────┐
│ 中间证书 (Intermediate CA) │
│ /opt/cfssl/ca.pem │
│ 有效期: 10年 │
│ 角色: 签发所有终端证书 |
| CFSSL服务(签发所有终端证书) │
│ Nginx(验证客户端证书链) │ │
│ 私钥: ca-key.pem (仅CA服务器) │
└──────────────┬──────────────────────┘
│
│ 签发
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ 服务端证书 │ │ 服务端证书 │ │ ...... │
│ (Nginx用) │ │ (Nginx用) │ │ │
│ server.pem │ │ server.pem │ │ │
│ 有效期: 1年 │ │ 有效期: 1年 │ │ │
│ 角色: 验证服务器身份 │ │ 角色: 验证服务器身份 │ │ │
│ ──────────────── │ │ ──────────────── │ │ │
│ 每1年自动续期 │ │ 每1年自动续期 │ │ │
└──────────────────────┘ └──────────────────────┘ └──────────────────────┘
┌──────────────────────────────────────────────────────┐
│ │
│ 客户端证书 (设备证书) 批量签发 │
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ 设备证书 │ │ 设备证书 │
│ device_001.crt │ ...... │ device_NNN.crt │
│ CN=device_001 │ │ CN=device_NNN │
│ 有效期: 1年 │ │ 有效期: 1年 │
│ 角色: 设备身份认证 │ │ 角色: 设备身份认证 │
│ ──────────────── │ │ ──────────────── │
│ 设备本地生成私钥 │ │ 设备本地生成私钥 │
│ 通过CFSSL API申请 │ │ 通过CFSSL API申请 │
│ 每1年自动续期 │ │ 每1年自动续期 │
└──────────────────────┘ └──────────────────────┘二、生成根CA(有效期20年)
2.1 创建根CA配置文件 root-ca/root-ca.conf
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
prompt = no
[ req_distinguished_name ]
C = CN
ST = Guangdong
L = Shenzhen
O = MyCompany
OU = IoT Root CA
CN = My IoT Root CA
[ v3_ca ]
basicConstraints = critical, CA:TRUE
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer2.2 生成根CA私钥和自签名证书(有效期7300天 = 20年)
cd ~/pki/root-ca
openssl genrsa -out root-ca.key 2048
openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 7300 -config root-ca.conf -out root-ca.crt生成文件:
root-ca.key:根CA私钥(绝密,建议离线保存)root-ca.crt:根CA证书(公开,用于验证中间CA和最终证书)
三、生成中间CA(有效期10年)
3.1 创建中间CA CSR配置文件 intermediate-ca/intermediate-ca.conf
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[ req_distinguished_name ]
C = CN
ST = Guangdong
L = Shenzhen
O = MyCompany
OU = IoT Intermediate CA
CN = My IoT Intermediate CA
[ v3_req ]
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, digitalSignature, keyCertSign3.2 生成中间CA私钥和CSR
cd ~/pki/intermediate-ca
openssl genrsa -out intermediate-ca.key 2048
openssl req -new -key intermediate-ca.key -out intermediate-ca.csr -config intermediate-ca.conf3.3 使用根CA签发中间CA证书(有效期3650天 = 10年)
创建中间CA证书扩展配置文件 intermediate-ca/v3_intermediate_ca.conf:
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, digitalSignature, keyCertSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer签发命令:
cd ~/pki/intermediate-ca
openssl x509 -req -in intermediate-ca.csr \
-CA ../root-ca/root-ca.crt -CAkey ../root-ca/root-ca.key \
-CAcreateserial -out intermediate-ca.crt -days 3650 -sha256 \
-extfile v3_intermediate_ca.conf3.4 验证中间CA证书
openssl verify -CAfile ../root-ca/root-ca.crt intermediate-ca.crt
# 应输出:intermediate-ca.crt: OK四、准备CFSSL服务(使用中间CA签发终端证书)
4.1 创建CFSSL配置文件 cfssl/cfssl-config.json
终端证书有效期统一为 1年(8760小时),区分 server 和 client 用途。
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"server": {
"usages": ["signing", "key encipherment", "server auth"],
"expiry": "8760h"
},
"client": {
"usages": ["signing", "key encipherment", "client auth"],
"expiry": "8760h"
}
}
}
}4.2 准备中间CA证书供CFSSL使用
cd ~/pki/cfssl
cp ../intermediate-ca/intermediate-ca.crt ./ca.pem
cp ../intermediate-ca/intermediate-ca.key ./ca-key.pem4.3 启动CFSSL API服务(前台测试)
cfssl serve -address 0.0.0.0 -port 8888 -ca ca.pem -ca-key ca-key.pem -config cfssl-config.json生产环境:建议使用
systemd守护,配置文件路径改为/opt/cfssl/,并创建专用用户cfssl。
4.3 启动CFSSL API服务(生产环境)
systemd 服务示例(/etc/systemd/system/cfssl.service):
[Unit]
Description=CFSSL Certificate Authority API Server
Documentation=https://github.com/cloudflare/cfssl
After=network.target
[Service]
Type=simple
User=cfssl
Group=cfssl
WorkingDirectory=/opt/cfssl
# 核心启动命令:监听 127.0.0.1:8888
ExecStart=/usr/local/bin/cfssl serve \
-address 127.0.0.1 \
-port 8888 \
-ca /opt/cfssl/ca.pem \
-ca-key /opt/cfssl/ca-key.pem \
-config /opt/cfssl/cfssl-config.json
# 自动重启策略:异常退出后等待10秒重启
Restart=on-failure
RestartSec=10s
# 安全加固:禁止进程提权
NoNewPrivileges=yes
# 日志相关
StandardOutput=journal
StandardError=journal
SyslogIdentifier=cfssl
[Install]
WantedBy=multi-user.target创建专用系统用户:
# 创建无登录权限、无家目录的系统用户 cfssl
sudo useradd -r -s /bin/false -M cfssl参数说明:
-r创建系统账户,-s /bin/false禁止登录,-M不创建家目录。
创建数据目录并迁移文件:
# 创建目录
sudo mkdir -p /opt/cfssl
# 将之前生成好的中间CA证书、私钥和CFSSL配置文件复制过去
# (假设你之前在 ~/pki/cfssl/ 目录下操作)
sudo cp ~/pki/cfssl/ca.pem /opt/cfssl/
sudo cp ~/pki/cfssl/ca-key.pem /opt/cfssl/
sudo cp ~/pki/cfssl/cfssl-config.json /opt/cfssl/设置严格的文件权限(关键安全步骤)
# 将目录及其下所有文件的所有权赋给 cfssl 用户
sudo chown -R cfssl:cfssl /opt/cfssl
# 私钥文件仅允许所有者读写(权限 600),绝对不能让其他用户读取
sudo chmod 600 /opt/cfssl/ca-key.pem
# 证书和配置文件可读即可(权限 644)
sudo chmod 644 /opt/cfssl/ca.pem
sudo chmod 644 /opt/cfssl/cfssl-config.json创建 Systemd 服务单元文件
创建 /etc/systemd/system/cfssl.service 文件:
sudo vim /etc/systemd/system/cfssl.service把上面的 systemd 服务示例写入到文件
重载 Systemd 并启动服务
# 重新加载 systemd 配置
sudo systemctl daemon-reload
# 设置开机自启
sudo systemctl enable cfssl
# 立即启动服务
sudo systemctl start cfssl
# 查看运行状态
sudo systemctl status cfssl验证服务是否正常运行
方式一:检查端口监听
sudo netstat -tlnp | grep 8888
# 应显示 tcp 127.0.0.1:8888,且进程名为 cfssl
# 或者
lsof -i:8888方式二:调用 API 测试接口
curl -X POST http://127.0.0.1:8888/api/v1/cfssl/info -H "Content-Type: application/json" -d '{}'
# 正常应返回 JSON 格式的 CA 信息方式三:查看实时日志(排查问题用)
sudo journalctl -u cfssl -f调整设备端 API 调用地址
由于现在 CFSSL 仅监听 127.0.0.1,外部设备无法直接访问。你需要在 CFSSL 服务器上部署一个 Nginx 反向代理,或者设备通过内网负载均衡器访问。
添加一个 http 块的反向代理,用于接收设备发来的证书申请请求,并转发给本机的 127.0.0.1:8888,最好使用https
server {
listen 80;
server_name ca.yourdomain.com; # 给 CA 服务单独分配一个域名
# 建议加个简单的 API Token 防暴力调用(可选)
location /api/ {
proxy_pass http://127.0.0.1:8888/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}五、签发服务端证书(供Nginx使用,有效期1年)
5.1 准备CSR配置 server/server-csr.json
注意:CN 必须为客户端实际访问的域名(或IP)
{
"CN": "emqx.test.com",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "CN",
"ST": "Guangdong",
"L": "Shenzhen",
"O": "Wenjy"
}
]
}5.2 使用CFSSL签发服务端证书
cd ~/pki/server
cfssl gencert \
-ca=../cfssl/ca.pem \
-ca-key=../cfssl/ca-key.pem \
-config=../cfssl/cfssl-config.json \
-profile=server \
server-csr.json | cfssljson -bare server生成文件:server.pem(证书)、server-key.pem(私钥)
5.3 构建完整的服务端证书链(供Nginx使用)
Nginx 需要完整的证书链(服务器证书 + 中间CA),以便客户端(设备)能验证完整路径。
cat server.pem ../intermediate-ca/intermediate-ca.crt > server-fullchain.pem六、签发客户端证书(设备用,有效期1年)
6.1 准备CSR配置 client/client-csr.json
每台设备的 CN 必须是唯一的(如设备序列号)。
{
"CN": "device_sn_001",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "CN",
"ST": "Guangdong",
"L": "Shenzhen",
"O": "Wenjy"
}
]
}6.2 签发客户端证书
cd ~/pki/client
cfssl gencert \
-ca=../cfssl/ca.pem \
-ca-key=../cfssl/ca-key.pem \
-config=../cfssl/cfssl-config.json \
-profile=client \
client-csr.json | cfssljson -bare client生成:client.pem(证书)、client-key.pem(私钥)
批量签发:循环修改 CN 字段,重复执行上述命令即可。
七、准备信任链文件(供Nginx验证客户端)
Nginx 验证客户端证书时,需要完整的信任链(根CA + 中间CA)。
cd ~/pki
cat root-ca/root-ca.crt intermediate-ca/intermediate-ca.crt > ca-chain.crt八、配置Nginx实现mTLS
8.1 Nginx stream 配置 /etc/nginx/conf.d/mqtts.conf
stream 需要看你的具体配置,如果原来有配置了stream 引入文件,那就行去掉外围的stream就行
stream {
upstream emqx_backend {
server 127.0.0.1:1883;
# 若有多个EMQX节点,可添加
# server 192.168.1.101:1883;
}
server {
listen 8883 ssl;
server_name emqx.test.com;
# 服务端证书链(服务器证书 + 中间CA)
ssl_certificate /usr/local/nginx/conf/ssl/mqtt/server-fullchain.pem;
ssl_certificate_key /usr/local/nginx/conf/ssl/mqtt/server-key.pem;
# 客户端证书验证链(根CA + 中间CA)
ssl_client_certificate /usr/local/nginx/conf/ssl/mqtt/ca-chain.crt;
ssl_verify_client on;
ssl_verify_depth 2;
# SSL 优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
#ssl_session_cache shared:SSL:10m;
#ssl_session_timeout 10m;
# 传递真实客户端IP(必须)
proxy_protocol on;
proxy_pass emqx_backend;
proxy_connect_timeout 5s;
}
}8.2 复制证书并重载Nginx
复制证书到nginx目录
sudo mkdir -p /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/server/server-fullchain.pem /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/server/server-key.pem /usr/local/nginx/conf/ssl/mqtt
sudo cp ~/pki/ca-chain.crt /usr/local/nginx/conf/ssl/mqtt
# 关键:设置严格权限
sudo chmod 600 /usr/local/nginx/conf/ssl/mqtt/server-key.pem
sudo nginx -t && sudo nginx -s reload九、配置EMQX(开启PROXY协议)
EMQX 5.8 社区版支持 proxy_protocol,用于获取客户端真实IP。
方式一:配置文件(/etc/emqx/emqx.conf 或 cluster.hocon)
listeners.tcp.default {
bind = "0.0.0.0:1883"
proxy_protocol = true
proxy_protocol_timeout = 3s
}方式二:Dashboard
访问 EMQX Dashboard → 管理 → 监听器 → 找到 tcp:default(1883)
编辑 → 开启 Proxy Protocol → 保存并重启监听器
十、设备端集成(轻量级密码库)
设备(MCU/嵌入式)需使用轻量级密码库(如 wolfSSL 或 Mbed TLS)完成以下操作:
在设备本地生成私钥和CSR(私钥永不离开设备)。
将CSR(Base64编码)通过HTTP POST发送给CFSSL API(http://CA_IP:8888/api/v1/cfssl/newcert)。
接收返回的证书并保存到本地存储。
使用该证书(及私钥)连接Nginx的8883端口,完成mTLS握手。
设备端伪代码(Python示例,仅用于理解逻辑):
import subprocess, requests, base64
# 1. 生成私钥和CSR(实际用wolfSSL API,此处用openssl模拟)
subprocess.run("openssl genrsa -out device.key 2048", shell=True)
subprocess.run("openssl req -new -key device.key -out device.csr -subj '/CN=device_sn_001'", shell=True)
# 2. 提交CSR
with open('device.csr', 'rb') as f:
csr_b64 = base64.b64encode(f.read()).decode()
resp = requests.post('http://your-ca-server:8888/api/v1/cfssl/newcert',
json={'certificate_request': csr_b64})
cert_pem = resp.json()['result']['certificate']
# 3. 保存证书
with open('device.crt', 'w') as f:
f.write(cert_pem)真实设备请使用 wolfSSL 的 wolfSSL_CTX_use_certificate_chain_file 等函数管理证书。
十一、证书自动续期机制
11.1 设备端主动续期(推荐)
设备在每次启动或定期检查本地证书有效期,若剩余天数 < 30天,则触发续期流程:
生成新的私钥和CSR(可重新生成或复用旧私钥,视安全策略而定)。
调用CFSSL API申请新证书。
原子替换旧证书文件(保证连接不中断)。
使用新证书重新连接MQTT。
续期逻辑伪代码:
def check_and_renew():
if days_until_expiry('device.crt') < 30:
new_key, new_csr = generate_new_key_and_csr()
new_cert = request_cert_from_ca(new_csr)
replace_cert(new_cert, new_key)
mqtt_reconnect_with_new_cert()11.2 服务端证书续期
服务端证书(Nginx用)同样1年有效,可手动或使用脚本在过期前30天重新签发,然后执行 nginx -s reload。
十二、测试与验证
12.1 使用 mosquitto 客户端测试 mTLS 连接
# 安装 mosquitto-clients
sudo apt install mosquitto-clients -y
# 发布消息(需指定CA根证书验证服务端,以及客户端证书/私钥)
mosquitto_pub -h emqx.test.com -p 8884 \
--cafile ~/pki/root-ca/root-ca.crt \
--cert ~/pki/client/client.pem \
--key ~/pki/client/client-key.pem \
-u test -P test \
-t "test" -m "hello" -d12.1 使用MQTTX客户端验证
- 下载并安装 MQTTX
首先,你需要从官方渠道下载并安装 MQTTX 桌面客户端:
官方下载页面:https://mqttx.app/zh
EMQ 官方下载:https://www.emqx.com/zh/downloads/MQTTX
GitHub Releases:https://github.com/emqx/MQTTX/releases
选择适合你操作系统的版本(Windows、macOS、Linux)下载并安装即可
- 获取客户端证书(通过 CFSSL API)
你的设备(或测试电脑)需要向 CFSSL 服务申请一张唯一的客户端证书。
第一步:生成设备私钥和证书签名请求(CSR)
在设备(或测试电脑)上,使用 openssl 命令生成:
# 1. 生成设备私钥(请妥善保管,切勿泄露)
openssl genrsa -out device.key 2048
# 2. 生成证书签名请求 (CSR),CN 必须填设备的唯一标识
openssl req -new -key device.key -out device.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=device_sn_001"
# 验证 CSR 中的 Subject
openssl req -in device.csr -noout -subject
# 应输出:subject=C=CN, ST=Guangdong, L=Shenzhen, O=Wenjy, CN=device_sn_001第二步:调用 CFSSL API 申请证书
将上一步生成的 device.csr 文件内容进行 Base64 编码,然后通过 curl 发送给 CFSSL 服务
# 如果没有安装 jq,先安装
sudo apt install jq -y # Ubuntu/Debian
CSR_JSON=$(cat device.csr | sed 's/$/\\n/' | tr -d '\n')
curl -X POST http://你的CA服务器IP:8888/api/v1/cfssl/newcert \
-H "Content-Type: application/json" \
-d "{\"certificate_request\": \"$CSR_JSON\", \"profile\": \"client\"}" \
| jq -r '.result.certificate' > device.crt第三步:保存证书,上面通过jq来保存
API 调用成功后,会返回一个 JSON 响应,其中 result.certificate 字段就是你的客户端证书(PEM 格式)。
将返回的证书内容保存为 device.crt 文件。最终的证书文件就是:
客户端证书:device.crt
客户端私钥:device.key
至于根证书 root-ca.crt
- 配置 SSL/TLS 证书(关键)
在 “SSL/TLS” 或 “安全” 选项卡中:
字段 值 说明
CA 文件 选择 root-ca.crt(根证书20年有效期) 用于验证服务端证书
客户端证书文件 选择 device.crt 你的设备证书
客户端密钥文件 选择 device.key 你的设备私钥
SSL 安全 开启(不可关闭) 确保验证服务端
注意:如果服务端证书是自签名的,必须指定 CA 文件,否则会报“证书验证失败”。
连接成功后,进行消息收发测试
12.2 服务端证书自动续期脚本 + Cron 定时任务
- 续期脚本
创建脚本文件 /usr/local/bin/renew_nginx_cert.sh:
#!/bin/bash
# =======================================================
# 服务端证书自动续期脚本 (Nginx + CFSSL)
# 功能:检查证书剩余天数,若小于30天则自动续期
# 适用:Ubuntu 20.04, Nginx, CFSSL API (127.0.0.1:8888)
# =======================================================
set -e
# ---------- 配置变量 ----------
DOMAIN="emqx.test.com" # 服务端证书域名
NGINX_SSL_DIR="/etc/nginx/ssl"
SERVER_KEY="${NGINX_SSL_DIR}/server-key.pem"
SERVER_CERT="${NGINX_SSL_DIR}/server-fullchain.pem"
CA_CHAIN="${NGINX_SSL_DIR}/ca-chain.crt"
CFSSL_API="http://127.0.0.1:8888/api/v1/cfssl/sign"
PROFILE="server" # 对应 cfssl-config.json 中的 profile
LOG_FILE="/var/log/renew_nginx_cert.log"
# ---------- 函数:记录日志 ----------
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# ---------- 1. 检查证书剩余天数 ----------
if [ ! -f "$SERVER_CERT" ]; then
log "错误: 证书文件 $SERVER_CERT 不存在,无法检查有效期。"
exit 1
fi
# 提取证书过期日期
exp_date=$(openssl x509 -in "$SERVER_CERT" -noout -enddate | cut -d= -f2)
exp_epoch=$(date -d "$exp_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (exp_epoch - now_epoch) / 86400 ))
log "当前证书剩余天数: $days_left 天"
# 如果剩余天数大于30天,无需续期
if [ "$days_left" -gt 30 ]; then
log "证书有效期充足,无需续期。"
exit 0
fi
log "证书将在 ${days_left} 天后过期,开始续期流程..."
# ---------- 2. 生成新的 CSR(使用现有私钥) ----------
# 注意:我们沿用旧的 server-key.pem,只更新证书。
# 如果希望每次轮换私钥,可在此处生成新私钥并替换,但需额外处理。
WORK_DIR=$(mktemp -d)
cd "$WORK_DIR"
# 生成 CSR(使用原私钥)
openssl req -new -key "$SERVER_KEY" -out server.csr \
-subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=${DOMAIN}"
# 添加 SAN 扩展(如果需要)
# 由于 CFSSL 配置中会覆盖 SAN,这里可以忽略,但为了保险,我们生成带 SAN 的 CSR:
# 先生成 openssl 配置文件,但为简化,使用 -addext 参数(OpenSSL 1.1.1+ 支持)
openssl req -new -key "$SERVER_KEY" -out server.csr \
-subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Wenjy/CN=${DOMAIN}" \
-addext "subjectAltName=DNS:${DOMAIN}"
log "CSR 生成完成"
# ---------- 3. 将 CSR 转换为 JSON 字符串(转义换行) ----------
CSR_JSON=$(cat server.csr | sed 's/$/\\n/' | tr -d '\n')
# ---------- 4. 调用 CFSSL API 签名 ----------
response=$(curl -s -X POST "$CFSSL_API" \
-H "Content-Type: application/json" \
-d "{\"certificate_request\": \"$CSR_JSON\", \"profile\": \"$PROFILE\"}")
# 检查是否成功
success=$(echo "$response" | jq -r '.success')
if [ "$success" != "true" ]; then
error_msg=$(echo "$response" | jq -r '.errors[0].message')
log "CFSSL 签名失败: $error_msg"
rm -rf "$WORK_DIR"
exit 1
fi
# ---------- 5. 提取新证书 ----------
new_cert=$(echo "$response" | jq -r '.result.certificate')
if [ -z "$new_cert" ] || [ "$new_cert" = "null" ]; then
log "响应中未包含证书内容"
rm -rf "$WORK_DIR"
exit 1
fi
# 将新证书保存到临时文件
echo "$new_cert" > server_new.crt
# ---------- 6. 构建完整的证书链(服务器证书 + 中间CA) ----------
# 假设中间CA证书位于 /opt/cfssl/ca.pem(即中间证书)
INTERMEDIATE_CA="/opt/cfssl/ca.pem"
if [ ! -f "$INTERMEDIATE_CA" ]; then
log "警告: 中间CA证书 $INTERMEDIATE_CA 不存在,将仅使用服务器证书"
cat server_new.crt > server_fullchain_new.pem
else
cat server_new.crt "$INTERMEDIATE_CA" > server_fullchain_new.pem
fi
# ---------- 7. 备份旧证书,替换新证书 ----------
TIMESTAMP=$(date +%Y%m%d%H%M%S)
cp "$SERVER_CERT" "${SERVER_CERT}.bak.${TIMESTAMP}"
cp "$SERVER_KEY" "${SERVER_KEY}.bak.${TIMESTAMP}"
cp server_fullchain_new.pem "$SERVER_CERT"
# 私钥不变(我们用的是原私钥),所以无需替换 server-key.pem
log "证书文件已更新"
# ---------- 8. 测试 Nginx 配置并重载 ----------
if /usr/local/nginx/sbin/nginx -t; then
/usr/local/nginx/sbin/nginx -s reload
log "Nginx 重载成功,新证书已生效"
else
# 如果测试失败,回滚证书
log "Nginx 配置测试失败,正在回滚证书..."
cp "${SERVER_CERT}.bak.${TIMESTAMP}" "$SERVER_CERT"
log "回滚完成,请手动检查 Nginx 配置"
rm -rf "$WORK_DIR"
exit 1
fi
# ---------- 9. 清理 ----------
rm -rf "$WORK_DIR"
log "服务端证书续期完成"
exit 0- 赋予脚本执行权限
sudo chmod +x /usr/local/bin/renew_nginx_cert.sh- 测试脚本(手动执行一次)
sudo /usr/local/bin/renew_nginx_cert.sh观察日志输出,确保没有错误。如果手动执行成功,Nginx 证书被更新并重载。
- 设置 Cron 定时任务
编辑 root 用户的 crontab:sudo crontab -e
添加以下内容(每天凌晨 3:00 执行):
0 3 * * * /usr/local/bin/renew_nginx_cert.sh >> /var/log/renew_nginx_cert.log 2>&1- 日志轮转(避免日志文件过大)
创建 /etc/logrotate.d/renew_nginx_cert:
/var/log/renew_nginx_cert.log {
daily
rotate 30
compress
missingok
notifempty
}