22 KiB
CertCenter - 轻量化证书管理中心
一站式 SSL 证书管理平台,集成 ACME 证书申请、自动续签、多服务器分发,通过 Web UI 完成所有操作。
目录
架构概览
┌─────────────────────────────────────────────────────────┐
│ CertCenter 服务 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ WebUI │ │ ACME 引擎 │ │ 数据库 │ │
│ │ (Vue) │ │ (certbot) │ │ (SQLite) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────┴──────────────┘ │
│ │ │
│ FastAPI 后端服务 │
└──────────────────────┬──────────────────────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ VPS-A │ │ VPS-B │ │ VPS-C │
│ Linux │ │ Linux │ │ Windows │
│ cron │ │ cron │ │ schtasks│
└─────────┘ └─────────┘ └─────────┘
核心流程:
- 在 WebUI 配置 ACME 凭据(阿里云 DNS / Cloudflare)
- 添加服务器和域名信息
- 点击"申请证书",CertCenter 自动完成 DNS 验证并签发证书
- 业务服务器通过 cron 定时拉取最新证书
- 证书到期前自动续签
环境要求
服务端(CertCenter)
| 依赖 | 版本 | 说明 |
|---|---|---|
| Python | 3.11 ~ 3.12 | ⚠️ 不支持 3.14(pydantic-core 无预编译 wheel) |
| certbot | 2.10+ | ACME 证书申请 |
| certbot-dns-alicloud | 最新 | 阿里云 DNS 插件(如使用阿里云) |
客户端(业务服务器)
| 依赖 | 说明 |
|---|---|
| bash / PowerShell | 执行部署脚本 |
| curl / Invoke-WebRequest | 下载证书 |
| cron / schtasks | 定时任务 |
安装部署
0. 确认 Python 版本
python --version
# 需要 3.11.x 或 3.12.x,不支持 3.14
如果版本不对,先装 Python 3.12:
- 下载地址:https://www.python.org/downloads/release/python-3128/
- 安装时勾选 "Add python.exe to PATH"
1. 克隆项目
git clone <repo-url> certcenter
cd certcenter
2. 创建虚拟环境并安装依赖
# 创建虚拟环境(指定 Python 3.12)
py -3.12 -m venv .venv
# 激活虚拟环境
.venv\Scripts\activate # Windows
# source .venv/bin/activate # Linux/Mac
# 安装依赖
pip install -r requirements.txt
如果使用阿里云 DNS,还需要安装 certbot 插件:
pip install certbot-dns-alicloud
3. 安装前端依赖并构建
cd frontend
npm install
npm run build
cd ..
构建完成后,frontend/dist/ 目录包含前端静态文件,FastAPI 会自动加载。
4. 配置环境变量
cp .env .env
编辑 .env 文件:
# 数据库路径(SQLite)
DATABASE_URL=sqlite+aiosqlite:///./certcenter.db
# 证书存储目录
CERT_DIR=/srv/certs
# CertCenter 的访问地址(客户端通过此地址拉取证书)
BASE_URL=https://cert.example.com
5. 启动服务
python -m backend.main
服务默认监听 http://0.0.0.0:8000。
首次启动时,系统会自动为 CertCenter 的域名生成一份自签证书(存放在 CERT_DIR 下),确保 HTTPS 可用。后续正式签发证书后会自动替换。
6. 引导流程(首次使用)
首次使用时的推荐操作顺序:
1. 启动 CertCenter(自签证书自动生成)
python -m backend.main
2. 打开 WebUI,配置 ACME 凭据(阿里云 AK/SK)
3. 添加域名:cert.zhzp.top(CertCenter 自身的域名)
4. 点击"申请证书",为 CertCenter 签发正式证书
5. 重启 CertCenter,正式证书自动加载
# 此时所有客户端连接都是受信任的 HTTPS
为什么需要这个流程?
CertCenter 使用 HTTPS → 需要证书 → 证书由 CertCenter 自己签发
↑
这就是"引导"问题
解决方式:先用自签证书启动 → 签发正式证书 → 替换。客户端脚本已内置 curl -k 跳过 TLS 验证,不受影响。
7. 生产环境部署(可选)
使用 systemd 托管服务:
cat > /etc/systemd/system/certcenter.service << 'EOF'
[Unit]
Description=CertCenter Service
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/certcenter
ExecStart=/opt/certcenter/venv/bin/python -m backend.main
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now certcenter
如果需要 HTTPS 访问 CertCenter 本身,可以使用 nginx 反向代理:
server {
listen 443 ssl;
server_name cert.example.com;
ssl_certificate /etc/nginx/ssl/cert.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/cert.example.com/private.key;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
测试验证
部署完成后,建议按以下步骤验证系统是否正常工作。
1. 环境检查
运行部署验证脚本,检查所有依赖和配置:
python -m tests.test_setup
该脚本会检查:
| 检查项 | 说明 |
|---|---|
| Python 依赖 | fastapi、sqlalchemy、acme、cryptography 等 |
| Certbot | certbot 命令是否可用 |
| DNS 插件 | certbot-dns-alicloud 或 certbot-dns-cloudflare |
| .env 配置 | 环境变量文件是否存在 |
| 数据库 | 数据库连接是否正常 |
| 前端构建 | frontend/dist/ 是否存在 |
| 证书目录 | 存储目录是否可写 |
| BASE_URL | 是否已修改默认值 |
| Let's Encrypt 连通性 | Staging 和 Production 环境是否可达 |
| FastAPI 服务 | 服务能否正常启动 |
所有检查通过后,进入下一步。
2. 端到端测试
使用 Let's Encrypt Staging 环境(测试环境)验证完整的证书签发流程。
前提条件:
- 一个使用阿里云 DNS 解析的域名
- 阿里云 RAM 子账号的 AccessKey(需有
AliyunDNSFullAccess权限) - CertCenter 服务已启动
执行测试:
python -m tests.test_e2e \
--domain test.example.com \
--email admin@example.com \
--ak YOUR_ACCESS_KEY \
--sk YOUR_ACCESS_SECRET
测试流程:
[0] 检查 API 服务是否正常
[1] 配置 ACME(Staging 环境 + 阿里云 DNS 凭据)
[2] 创建测试服务器
[3] 创建测试域名配置
[4] 申请证书(自动完成 DNS-01 验证)
[5] 检查证书信息(签发者、有效期、SAN)
[6] 测试版本接口
[7] 测试脚本生成
[8] 测试证书下载
预期输出:
==================================================
端到端测试: test.example.com
==================================================
✅ [0] API 服务正常
✅ [1] ACME 配置完成
✅ [2] 服务器 ID: 1
✅ [3] 域名 ID: 1
✅ [4] 证书申请成功: Certificate issued, expires: 2026-10-16 00:00:00
✅ [5] 签发者: CN=(STAGING) Artificial Apricot R3, O=Let's Encrypt
✅ [5] 有效期: 2026-07-18 ~ 2026-10-16
✅ [5] 域名: ['test.example.com']
✅ [5] 剩余天数: 90
✅ [6] 版本号: 1
✅ [7] 脚本生成成功,长度: 1234 字符
✅ [8] 证书下载成功
==================================================
✅ 端到端测试完成!
==================================================
证书位置: /tmp/cert-e2e-test/test.example.com/
Staging 证书不受浏览器信任,仅用于验证流程。
确认流程正常后,在 WebUI 中切换到生产环境重新签发。
Staging vs Production:
| 环境 | 地址 | 说明 |
|---|---|---|
| Staging | https://acme-staging-v02.api.letsencrypt.org/directory |
测试环境,证书不受信任,无速率限制 |
| Production | https://acme-v02.api.letsencrypt.org/directory |
生产环境,正式证书,有速率限制 |
建议: 先用 Staging 环境完成全流程测试,确认无误后在 WebUI 中切换到 Production 环境,删除测试域名,重新添加正式域名并签发。
WebUI 操作指南
启动服务后,浏览器访问 http://服务器IP:8000(或配置的域名)。
1. 仪表盘
首页展示:
- 统计卡片:服务器总数、域名总数、即将过期证书数
- 最近部署日志:最近 10 条客户端部署记录
- 快捷操作:新增服务器、新增域名
2. 服务器管理
新增服务器:
- 点击侧边栏"服务器管理"
- 点击"+ 新增服务器"
- 填写表单:
- 名称:服务器标识,如
vps-blog、api-server - 平台:选择
Linux或Windows - Token:点击"生成"按钮自动生成,或手动填写
- IP:可选,仅用于备注
- 名称:服务器标识,如
- 点击"创建"
编辑/删除服务器:
- 在列表中点击"编辑"修改信息
- 点击"删除"会同时删除该服务器下的所有域名配置
3. 域名管理
新增域名:
- 点击侧边栏"域名管理"
- 点击"+ 新增域名"
- 填写表单:
- 所属服务器:选择域名所在的业务服务器
- 域名:如
example.com、api.example.com - 证书存放路径:证书在业务服务器上的存放目录
- Linux 示例:
/etc/nginx/ssl/example.com - Windows 示例:
C:\certs\example.com
- Linux 示例:
- 校验命令:替换证书后验证服务是否正常的命令
- nginx 示例:
nginx -t - 其他服务根据实际情况填写
- nginx 示例:
- 重载命令:证书更新后重载服务的命令
- Linux 示例:
systemctl reload nginx - Windows 示例:
nginx -s reload
- Linux 示例:
- 点击"创建"
申请证书:
- 在域名列表中找到目标域名
- 点击"申请证书"按钮
- 系统自动完成:
- 通过 DNS-01 验证(自动添加
_acme-challengeTXT 记录) - 向 Let's Encrypt 申请证书
- 保存证书到配置的目录
- 更新证书到期时间
- 通过 DNS-01 验证(自动添加
- 申请成功后,"证书到期"列会显示到期日期
复制部署命令:
点击"复制部署命令",获取在业务服务器上执行的一键部署命令。
查看脚本:
点击"查看脚本",预览将下发到业务服务器的部署脚本内容。
4. ACME 配置
基本配置:
- 点击侧边栏"ACME 配置"
- 配置项:
- ACME 服务器:
- Let's Encrypt (生产):正式环境使用
- Let's Encrypt (测试):测试环境,不受速率限制
- 邮箱:Let's Encrypt 注册邮箱,用于接收证书到期提醒
- DNS 提商:选择使用的 DNS 服务商
- 续签提前天数:证书到期前几天自动续签(默认 30 天)
- ACME 服务器:
DNS 凭据配置:
根据选择的 DNS 提商填写对应的凭据:
| DNS 提商 | 需要的凭据 |
|---|---|
| 阿里云 DNS | Access Key + Access Secret |
| Cloudflare | API Token |
获取阿里云 DNS 凭据:
- 登录 阿里云控制台
- 创建子账号,授予
AliyunDNSFullAccess权限 - 创建 AccessKey,记录 Access Key ID 和 Access Key Secret
自动续签:
点击"🔄 自动续签所有"按钮,系统会:
- 检查所有域名的证书到期时间
- 对即将过期的证书自动执行续签
- 显示每个域名的续签结果
5. 部署日志
查看所有客户端(业务服务器)的证书拉取部署记录:
- 时间:部署执行时间
- 服务器:执行部署的服务器
- 域名:部署的域名
- 状态:成功 / 失败 / 跳过
- 消息:详细信息
支持按状态筛选。
6. ACME 日志
查看所有证书申请/续签操作记录:
- 时间:操作时间
- 域名:操作的域名
- 操作:申请 / 续签 / 吊销 / 注册
- 状态:成功 / 失败 / 进行中
- 消息:结果信息
- 详情:点击"查看"显示完整日志
客户端部署指南
Linux 服务器
首次部署(只需执行一次):
# 1. 下载部署脚本(从 WebUI "复制部署命令" 获取)
curl -fsSL "https://cert.example.com/api/script/example.com?server_name=vps-blog" \
-o /usr/local/bin/deploy-cert.sh
# 2. 添加执行权限
chmod +x /usr/local/bin/deploy-cert.sh
# 3. 添加 cron 定时任务(每 30 分钟检查一次)
echo '*/30 * * * * /usr/local/bin/deploy-cert.sh >> /var/log/deploy-cert.log 2>&1' | crontab -
手动执行测试:
/usr/local/bin/deploy-cert.sh
查看部署日志:
tail -f /var/log/deploy-cert.log
脚本执行流程:
1. 请求 CertCenter 获取远端版本号
2. 对比本地版本号
├── 一致 → 退出(无需更新)
└── 不一致 ↓
3. 下载新证书(fullchain.pem + private.key)
4. 备份旧证书
5. 替换新证书
6. 执行校验命令(nginx -t)
├── 失败 → 回滚旧证书 → 退出
└── 成功 ↓
7. 更新本地版本号
8. 重载服务(systemctl reload nginx)
Windows 服务器
首次部署:
# 1. 创建脚本目录
New-Item -ItemType Directory -Force -Path C:\scripts
# 2. 下载部署脚本
Invoke-WebRequest `
-Uri "https://cert.example.com/api/script/www.example.com?server_name=win-web" `
-OutFile C:\scripts\deploy-cert.ps1
# 3. 添加计划任务(每 30 分钟)
schtasks /create /sc minute /mo 30 /tn "CertDeploy-www.example.com" /tr "powershell -ExecutionPolicy Bypass -File C:\scripts\deploy-cert.ps1"
手动执行测试:
powershell -ExecutionPolicy Bypass -File C:\scripts\deploy-cert.ps1
API 参考
客户端 API
供业务服务器上的部署脚本调用,使用 Bearer Token 认证。
获取证书版本号
GET /api/version/{domain}
- 认证:无需认证
- 返回:版本号字符串(纯文本)
示例:
curl https://cert.example.com/api/version/example.com
# 返回: 3
下载证书
GET /api/cert/{domain}/fullchain
GET /api/cert/{domain}/private
- 认证:
Authorization: Bearer <token> - 返回:PEM 格式证书文件
示例:
curl -H "Authorization: Bearer tok_xxx" \
https://cert.example.com/api/cert/example.com/fullchain \
-o fullchain.pem
获取部署脚本
GET /api/script/{domain}?server_name={server_name}
- 认证:无需认证
- 返回:部署脚本内容(bash 或 PowerShell)
示例:
curl https://cert.example.com/api/script/example.com?server_name=vps-blog \
-o /usr/local/bin/deploy-cert.sh
上报部署结果
POST /api/report/{domain}?status={status}&message={message}
- 认证:
Authorization: Bearer <token> - 参数:
status:success/failed/skippedmessage:可选,详细信息
示例:
curl -X POST \
-H "Authorization: Bearer tok_xxx" \
"https://cert.example.com/api/report/example.com?status=success&message=updated+to+v3"
管理 API
供 WebUI 前端调用,所有接口以 /admin/api 为前缀。
统计数据
GET /admin/api/stats
返回:
{
"server_count": 3,
"domain_count": 5,
"expiring_count": 1,
"recent_logs": [...]
}
服务器管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/api/servers |
服务器列表 |
| GET | /admin/api/servers/{id} |
服务器详情 |
| POST | /admin/api/servers |
新增服务器 |
| PUT | /admin/api/servers/{id} |
编辑服务器 |
| DELETE | /admin/api/servers/{id} |
删除服务器 |
新增/编辑请求体:
{
"name": "vps-blog",
"platform": "linux",
"token": "tok_xxxxxxxxxxxx",
"ip": "1.2.3.4"
}
域名管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/api/domains |
域名列表 |
| GET | /admin/api/domains/{id} |
域名详情 |
| POST | /admin/api/domains |
新增域名 |
| PUT | /admin/api/domains/{id} |
编辑域名 |
| DELETE | /admin/api/domains/{id} |
删除域名 |
新增/编辑请求体:
{
"server_id": 1,
"domain": "example.com",
"cert_dir": "/etc/nginx/ssl/example.com",
"check_cmd": "nginx -t",
"reload_cmd": "systemctl reload nginx"
}
ACME 配置
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/api/acme/config |
获取 ACME 配置 |
| PUT | /admin/api/acme/config |
更新 ACME 配置 |
更新请求体:
{
"acme_server": "https://acme-v02.api.letsencrypt.org/directory",
"email": "admin@example.com",
"dns_provider": "aliyun",
"dns_credentials": "{\"access_key\":\"xxx\",\"access_secret\":\"xxx\"}",
"renew_days": 30
}
ACME 操作
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /admin/api/acme/issue/{domain_id} |
申请证书 |
| POST | /admin/api/acme/renew/{domain_id} |
续签证书 |
| POST | /admin/api/acme/auto-renew |
自动续签所有 |
日志查询
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /admin/api/logs?status={status} |
部署日志 |
| GET | /admin/api/acme/logs?status={status} |
ACME 日志 |
证书信息
GET /admin/api/cert-info/{domain_id}
返回:
{
"domain": "example.com",
"cert_info": {
"subject": "CN=example.com",
"issuer": "CN=R3, O=Let's Encrypt",
"not_before": "2026-01-01T00:00:00",
"not_after": "2026-04-01T00:00:00",
"serial_number": "1234567890",
"san": ["example.com"]
},
"need_renew": false,
"days_left": 75
}
配说说明
环境变量(.env)
| 变量 | 默认值 | 说明 |
|---|---|---|
DATABASE_URL |
sqlite+aiosqlite:///./certcenter.db |
数据库连接字符串 |
CERT_DIR |
/srv/certs |
证书存储根目录 |
BASE_URL |
https://cert.example.com |
CertCenter 访问地址 |
证书存储目录结构
/srv/certs/
├── example.com/
│ ├── fullchain.pem # 完整证书链
│ ├── private.key # 私钥
│ └── .version # 当前版本号
├── api.example.com/
│ ├── fullchain.pem
│ ├── private.key
│ └── .version
└── .certbot-work/ # certbot 工作目录
├── .certbot-config/
└── .certbot-logs/
版本号说明
版本号为递增整数,初始值为 0。每次证书申请/续签成功后自动 +1。
客户端通过对比版本号判断是否需要下载新证书,避免重复下载。
常见问题
Q: 申请证书时报错 "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.xxx"
原因: DNS TXT 记录未正确添加或未及时传播。
解决方案:
- 确认阿里云 DNS 凭据正确(Access Key / Secret)
- 确认域名使用的是阿里云 DNS 服务
- 等待 DNS 记录传播(通常 1-2 分钟)
- 使用测试环境(Let's Encrypt Staging)调试
Q: 客户端脚本执行后没有更新证书
可能原因:
- 版本号一致,无需更新(正常行为)
- Token 不正确,API 返回 401
- 网络不通,无法访问 CertCenter
排查步骤:
# 1. 手动测试版本接口
curl https://cert.example.com/api/version/example.com
# 2. 手动测试证书下载
curl -H "Authorization: Bearer tok_xxx" \
https://cert.example.com/api/cert/example.com/fullchain
# 3. 查看脚本详细执行过程
bash -x /usr/local/bin/deploy-cert.sh
Q: nginx -t 校验失败,证书已回滚
原因: 新证书与 nginx 配置不匹配。
排查步骤:
# 1. 检查 nginx 配置
nginx -t
# 2. 检查证书内容
openssl x509 -in /etc/nginx/ssl/example.com/fullchain.pem -text -noout
# 3. 检查私钥是否匹配
openssl x509 -noout -modulus -in /etc/nginx/ssl/example.com/fullchain.pem | md5sum
openssl rsa -noout -modulus -in /etc/nginx/ssl/example.com/private.key | md5sum
Q: 如何更换 DNS 提商
- 在 WebUI 的"ACME 配置"页面修改"DNS 提商"下拉框
- 填写新提商的凭据
- 点击"保存凭据"
- 重新申请证书
Q: 如何备份和迁移
备份:
# 备份数据库
cp certcenter.db certcenter.db.bak
# 备份证书目录
tar czf certs-backup.tar.gz /srv/certs/
# 备份配置
cp .env .env.bak
迁移:
- 在新服务器部署项目
- 恢复数据库和证书目录
- 修改
.env中的BASE_URL为新地址 - 更新业务服务器脚本中的地址(或重新执行部署命令)
Q: 如何添加通配符证书
通配符证书需要 DNS 验证,CertCenter 已支持。在域名管理中添加:
域名: *.example.com
申请时系统会自动通过 DNS-01 验证完成签发。
Q: Let's Encrypt 速率限制
Let's Encrypt 有以下限制:
| 限制类型 | 限制值 |
|---|---|
| 每个域名每周证书数 | 50 张 |
| 每个域名每小时重复证书数 | 5 张 |
| 注册账户数 | 每个 IP 每小时 10 个 |
建议: 先使用测试环境(Staging)调试,确认无误后再切换到生产环境。