Skip to content

Webhook SSL证书配置

前面那些部署方式都是 yunplus 替你去操作某个具体产品。Webhook 反过来:yunplus 只负责在证书更新时把证书内容发给你,剩下的事情你自己决定怎么做。

适合这些情况:

  • 部署目标是 yunplus 暂未直接支持的产品或自建系统
  • 公司内部已有证书分发流程,只需要一个触发源
  • 想在证书更新时顺便做点别的事,比如通知群、写审计日志、触发 CI

配置有多简单

只有一个字段:请求地址

Webhook 部署表单

没有密钥、没有签名配置、没有请求方式选择——这些都是固定的,见下一节。

请求规格

项目
请求方法POST
Content-Typeapplication/json
请求地址你在表单里填的地址
触发时机每次证书签发成功后

请求体是证书对象,包含这些字段:

字段类型含义
idnumber证书在 yunplus 中的 ID
namestring证书名称 / 域名
pemstring证书公钥内容(PEM 格式,含证书链)
privateKeystring证书私钥内容(PEM 格式)

大致形如:

json
{
  "id": 12345,
  "name": "*.example.com",
  "pem": "-----BEGIN CERTIFICATE-----\nMIIF...\n-----END CERTIFICATE-----\n",
  "privateKey": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----\n"
}

第一次接入建议先把原始请求体打出来

用一个只做日志的接口先接一次,把收到的完整 JSON 打印出来存下,再按实际结构写解析逻辑。这样最稳妥,也方便日后排查。

整体流程

操作步骤

  1. 先把接收端服务部署好,确保能从公网访问。
  2. 进入 证书配置列表,点击目标证书的「自动部署」。
  3. 点击「新增自动部署」,在选择器左侧点「其他」,选择「Webhook」。

选择 Webhook

  1. 填入请求地址,点击「提交」。系统会立刻用当前证书触发一次,可以直接验证接口是否正常。

接收端示例

Node.js(Express)

javascript
const express = require('express');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const app = express();
app.use(express.json({ limit: '2mb' }));

// 用一个只有你知道的随机路径,避免被扫到
app.post('/hooks/cert/8f3a2b1c', (req, res) => {
  const { id, name, pem, privateKey } = req.body;

  if (!pem || !privateKey) {
    return res.status(400).json({ ok: false, msg: 'missing cert content' });
  }

  // 域名可能是 *.example.com,星号不能直接做文件名
  const safeName = name.replace(/\*/g, '_');
  const dir = '/etc/nginx/certs';

  fs.writeFileSync(path.join(dir, `${safeName}.pem`), pem, { mode: 0o644 });
  fs.writeFileSync(path.join(dir, `${safeName}.key`), privateKey, { mode: 0o600 });

  execSync('nginx -t && nginx -s reload');

  console.log(`[cert] updated ${name} (id=${id})`);
  res.json({ ok: true });
});

app.listen(3000);

Python(Flask)

python
import os
import subprocess
from flask import Flask, request, jsonify

app = Flask(__name__)
CERT_DIR = '/etc/nginx/certs'

@app.route('/hooks/cert/8f3a2b1c', methods=['POST'])
def receive_cert():
    data = request.get_json(silent=True) or {}
    pem = data.get('pem')
    private_key = data.get('privateKey')
    name = data.get('name', '')

    if not pem or not private_key:
        return jsonify(ok=False, msg='missing cert content'), 400

    safe_name = name.replace('*', '_')

    with open(os.path.join(CERT_DIR, f'{safe_name}.pem'), 'w') as f:
        f.write(pem)

    key_path = os.path.join(CERT_DIR, f'{safe_name}.key')
    with open(key_path, 'w') as f:
        f.write(private_key)
    os.chmod(key_path, 0o600)

    subprocess.run(['nginx', '-t'], check=True)
    subprocess.run(['nginx', '-s', 'reload'], check=True)

    app.logger.info('cert updated: %s', name)
    return jsonify(ok=True)


if __name__ == '__main__':
    app.run(port=3000)

安全建议

私钥通过 HTTP 传输,接收端的安全性完全由你自己保证。

这几件事一定要做

  1. 只用 HTTPS 地址。明文 HTTP 传私钥等于把私钥公开在链路上。
  2. 地址里带一段足够长的随机路径,比如 /hooks/cert/8f3a2b1c9d4e,相当于一个共享密钥,避免被扫描器发现。
  3. 校验请求体pemprivateKey 缺失时直接返回错误,不要写入残缺文件。
  4. 私钥文件权限设为 600,且不要放在 Web 可访问的目录下。
  5. 不要把私钥内容写进日志。日志里记域名和证书 ID 就够了。

有条件的话,再加一层来源 IP 限制或反向代理层的鉴权会更稳。

验证与排查

配置提交后,自动部署列表里会显示这个任务的执行状态。

接口没被调用:确认地址填写完整(含 https:// 和路径)、服务能从公网访问、防火墙放行了对应端口。

接口被调用但报错:先看你自己服务的日志。常见的是 body 解析失败(没配 JSON 解析中间件)、或请求体超过了默认大小限制(证书链内容可能有几 KB,示例里设了 2mb)。

写入成功但网站还是旧证书:检查 nginx -s reload 是否真的执行了。可以在接收端把 reload 的输出也记进日志。

想手动触发一次做测试:在证书列表里用「手动生成证书」签发一张新证书,会连带触发一遍部署任务。注意这会消耗一张剩余证书额度,见 证书订单与下载