DNSMgr 申请下来的证书,按照常见的 Nginx 使用方式,一般就是: ```text fullchain.pem private.key ``` 但我这边的 Poste.io 使用的证书文件是: ```text server.crt ca.crt server.key ``` 也就是说,不能直接把 `fullchain.pem` 原样覆盖进去,需要把完整证书链拆一下: ```text fullchain.pem ├── 第 1 张证书 → server.crt └── 后面的中间证书 → ca.crt private.key → server.key ``` 手动弄一次当然没什么问题,但证书本身就是自动续期的,如果每次续期之后还得手工拆证书,就没什么意义了。 所以让 AI 帮我写了一个部署脚本,放在: ```bash /usr/local/sbin/poste-cert-deploy ``` 目前使用的内容如下: ```bash #!/usr/bin/env bash set -Eeuo pipefail # Poste.io certificate deployer for dns.e5.tn SSH tasks. # dns.e5.tn uploads the renewed fullchain and private key to these temp paths # before invoking this script. SSL_DIR="/opt/poste/data/ssl" FULLCHAIN="$SSL_DIR/fullchain.pem.tmp" KEY_TMP="$SSL_DIR/server.key.tmp" SERVER_CRT="$SSL_DIR/server.crt" CA_CRT="$SSL_DIR/ca.crt" SERVER_KEY="$SSL_DIR/server.key" TMP_DIR="$SSL_DIR/.deploy-tmp" mkdir -p "$SSL_DIR" "$TMP_DIR" test -s "$FULLCHAIN" test -s "$KEY_TMP" # Split the first certificate (the leaf/server certificate) from the rest. awk ' /-----BEGIN CERTIFICATE-----/ { n++ } n == 1 { print } /-----END CERTIFICATE-----/ && n == 1 { exit } ' "$FULLCHAIN" > "$TMP_DIR/server.crt" awk ' /-----BEGIN CERTIFICATE-----/ { n++ } n >= 2 { print } ' "$FULLCHAIN" > "$TMP_DIR/ca.crt" # Require one leaf certificate and at least one intermediate certificate. test "$(grep -c '^-----BEGIN CERTIFICATE-----$' "$TMP_DIR/server.crt")" -eq 1 test "$(grep -c '^-----BEGIN CERTIFICATE-----$' "$TMP_DIR/ca.crt")" -ge 1 # Check that the certificate and private key belong together. CERT_FP="$(openssl x509 -in "$TMP_DIR/server.crt" -pubkey -noout | openssl pkey -pubin -outform DER 2>/dev/null | sha256sum | awk '{print $1}')" KEY_FP="$(openssl pkey -in "$KEY_TMP" -pubout -outform DER 2>/dev/null | sha256sum | awk '{print $1}')" test "$CERT_FP" = "$KEY_FP" # Install atomically, then apply Poste.io's expected permissions. install -o root -g root -m 0644 "$TMP_DIR/server.crt" "$SERVER_CRT" install -o root -g root -m 0644 "$TMP_DIR/ca.crt" "$CA_CRT" install -o root -g root -m 0600 "$KEY_TMP" "$SERVER_KEY" rm -f "$FULLCHAIN" "$KEY_TMP" rm -rf "$TMP_DIR" docker restart poste ``` 脚本做的事情其实不多。 DNSMgr 先通过 SSH 把新证书上传到: ```text /opt/poste/data/ssl/fullchain.pem.tmp /opt/poste/data/ssl/server.key.tmp ``` 然后调用这个脚本。 脚本会把 `fullchain.pem.tmp` 里的第一张证书提取出来作为: ```text server.crt ``` 剩下的中间证书作为: ```text ca.crt ``` 私钥则最终安装成: ```text server.key ``` 不过没有直接无脑覆盖。 正式替换之前,还会先检查证书文件是否正常,并通过 OpenSSL 分别提取证书和私钥里的公钥,计算 SHA256,确认两者确实是一对。 如果证书和私钥对不上: ```bash test "$CERT_FP" = "$KEY_FP" ``` 这里就会直接退出,避免把错误的私钥覆盖到 Poste.io 里。 最后再设置文件权限: ```text server.crt 0644 ca.crt 0644 server.key 0600 ``` 部署完成后删除临时文件,并执行: ```bash docker restart poste ``` 让 Poste.io 重新加载证书。 这样 DNSMgr 那边证书续期成功以后,就可以直接通过 SSH 上传证书并执行这个脚本,整个 Poste.io 的证书更新也就跟着自动完成了。 先记下来,免得过一段时间以后看到这个脚本,又想不起来当初为什么要拆 `fullchain.pem`。 Loading... DNSMgr 申请下来的证书,按照常见的 Nginx 使用方式,一般就是: ```text fullchain.pem private.key ``` 但我这边的 Poste.io 使用的证书文件是: ```text server.crt ca.crt server.key ``` 也就是说,不能直接把 `fullchain.pem` 原样覆盖进去,需要把完整证书链拆一下: ```text fullchain.pem ├── 第 1 张证书 → server.crt └── 后面的中间证书 → ca.crt private.key → server.key ``` 手动弄一次当然没什么问题,但证书本身就是自动续期的,如果每次续期之后还得手工拆证书,就没什么意义了。 所以让 AI 帮我写了一个部署脚本,放在: ```bash /usr/local/sbin/poste-cert-deploy ``` 目前使用的内容如下: ```bash #!/usr/bin/env bash set -Eeuo pipefail # Poste.io certificate deployer for dns.e5.tn SSH tasks. # dns.e5.tn uploads the renewed fullchain and private key to these temp paths # before invoking this script. SSL_DIR="/opt/poste/data/ssl" FULLCHAIN="$SSL_DIR/fullchain.pem.tmp" KEY_TMP="$SSL_DIR/server.key.tmp" SERVER_CRT="$SSL_DIR/server.crt" CA_CRT="$SSL_DIR/ca.crt" SERVER_KEY="$SSL_DIR/server.key" TMP_DIR="$SSL_DIR/.deploy-tmp" mkdir -p "$SSL_DIR" "$TMP_DIR" test -s "$FULLCHAIN" test -s "$KEY_TMP" # Split the first certificate (the leaf/server certificate) from the rest. awk ' /-----BEGIN CERTIFICATE-----/ { n++ } n == 1 { print } /-----END CERTIFICATE-----/ && n == 1 { exit } ' "$FULLCHAIN" > "$TMP_DIR/server.crt" awk ' /-----BEGIN CERTIFICATE-----/ { n++ } n >= 2 { print } ' "$FULLCHAIN" > "$TMP_DIR/ca.crt" # Require one leaf certificate and at least one intermediate certificate. test "$(grep -c '^-----BEGIN CERTIFICATE-----$' "$TMP_DIR/server.crt")" -eq 1 test "$(grep -c '^-----BEGIN CERTIFICATE-----$' "$TMP_DIR/ca.crt")" -ge 1 # Check that the certificate and private key belong together. CERT_FP="$(openssl x509 -in "$TMP_DIR/server.crt" -pubkey -noout | openssl pkey -pubin -outform DER 2>/dev/null | sha256sum | awk '{print $1}')" KEY_FP="$(openssl pkey -in "$KEY_TMP" -pubout -outform DER 2>/dev/null | sha256sum | awk '{print $1}')" test "$CERT_FP" = "$KEY_FP" # Install atomically, then apply Poste.io's expected permissions. install -o root -g root -m 0644 "$TMP_DIR/server.crt" "$SERVER_CRT" install -o root -g root -m 0644 "$TMP_DIR/ca.crt" "$CA_CRT" install -o root -g root -m 0600 "$KEY_TMP" "$SERVER_KEY" rm -f "$FULLCHAIN" "$KEY_TMP" rm -rf "$TMP_DIR" docker restart poste ``` 脚本做的事情其实不多。 DNSMgr 先通过 SSH 把新证书上传到: ```text /opt/poste/data/ssl/fullchain.pem.tmp /opt/poste/data/ssl/server.key.tmp ``` 然后调用这个脚本。 脚本会把 `fullchain.pem.tmp` 里的第一张证书提取出来作为: ```text server.crt ``` 剩下的中间证书作为: ```text ca.crt ``` 私钥则最终安装成: ```text server.key ``` 不过没有直接无脑覆盖。 正式替换之前,还会先检查证书文件是否正常,并通过 OpenSSL 分别提取证书和私钥里的公钥,计算 SHA256,确认两者确实是一对。 如果证书和私钥对不上: ```bash test "$CERT_FP" = "$KEY_FP" ``` 这里就会直接退出,避免把错误的私钥覆盖到 Poste.io 里。 最后再设置文件权限: ```text server.crt 0644 ca.crt 0644 server.key 0600 ``` 部署完成后删除临时文件,并执行: ```bash docker restart poste ``` 让 Poste.io 重新加载证书。 这样 DNSMgr 那边证书续期成功以后,就可以直接通过 SSH 上传证书并执行这个脚本,整个 Poste.io 的证书更新也就跟着自动完成了。 先记下来,免得过一段时间以后看到这个脚本,又想不起来当初为什么要拆 `fullchain.pem`。 最后修改:2026 年 09 月 05 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏