Files
common-server-public/certbot/README.md
张威33321 fb87be680d 0908
2026-09-08 20:36:17 +08:00

118 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Let's Encrypt 证书管理服务
独立的 certbot 服务,管理 SSL 证书的申请与自动续签。
## 架构
```
用户请求 ──▶ frps(远程 80/443) ──▶ frpc ──▶ nginx-proxy (80 / 8443)
ACME 验证 ◀── 直接读 certbot-webroot 卷
HTTPS ◀── 读 certbot-certs 卷(证书)
HTTPS 配置 ◀── 挂载 ./ssl.d证书就绪后才写入
```
certbot **不再常驻**:它只是一个一次性任务容器,通过 `docker compose run --rm` 执行。
## 为什么 HTTPS 配置要单独放
nginx 在加载配置时就要把证书读进内存。如果镜像里直接烤入带 `ssl_certificate`
server 块首次部署时证书不存在nginx 会 `[emerg] cannot load certificate` 直接退出;
而申请证书又必须要有 80 端口 —— 形成死锁。
所以拆成两段:
| 目录 | 内容 | 何时加载 |
|---|---|---|
| `nginx/conf.d/00-http.conf` | 80 端口ACME 验证 + 301 跳转 + /healthz | 烤进镜像,**始终加载** |
| `nginx/ssl.d.available/*.conf` | 8443 端口的 HTTPS server 块(模板) | 不直接加载 |
| `nginx/ssl.d/*.conf` | 由 `enable-ssl.sh` 从上面复制过来 | 挂进容器,证书就绪后才有内容 |
`nginx.conf` 末尾的 `include /etc/nginx/ssl.d/*.conf;` 在目录为空时不报错,
因此冷启动阶段 nginx 只加载 80 端口配置,可以正常启动。
## 使用方法
### 1. 首次申请证书(一键引导)
```bash
cd common-server-public/certbot
chmod +x init-cert.sh renew-cert.sh check-cert.sh ../nginx/enable-ssl.sh
./init-cert.sh
```
脚本依次完成:创建共享卷 → 启动 nginx此时无 HTTPS 配置)→ 申请证书 →
把已就绪域名的 HTTPS 配置写入 `ssl.d/` 并重载 nginx。
默认 5 个域名合成**一张 SAN 证书**,可用环境变量覆盖:
```bash
EMAIL=you@example.com \
DOMAINS="fastgpt.stonelan.cn gitea.stonelan.cn image.stonelan.cn registry.stonelan.cn www.stonelan.cn" \
./init-cert.sh
```
### 2. 配置自动续签
```bash
crontab -e
# 参考 crontab.example
# 0 2,15 * * * bash <绝对路径>/renew-cert.sh >> /var/log/certbot-renew.log 2>&1
# 0 8 * * * bash <绝对路径>/check-cert.sh >> /var/log/certbot-check.log 2>&1
```
- `renew-cert.sh`:续签,然后给 nginx 发 HUP 优雅重载
- `check-cert.sh`:剩余有效期低于 20 天时以非 0 退出,用于接告警
### 3. 手动操作
```bash
./renew-cert.sh # 手动续签
./check-cert.sh # 查看证书与剩余天数
../nginx/enable-ssl.sh # 单独启用 HTTPS 配置(部分域名没证书时会跳过)
docker compose run --rm certbot certificates
```
## 目录与卷
| 卷 / 目录 | 容器内路径 | 使用者 | 说明 |
|---|---|---|---|
| `certbot-certs` | `/etc/letsencrypt` | certbot 读写、nginx 只读 | 证书 |
| `certbot-webroot` | `/var/www/certbot` | certbot 写、nginx 只读 | ACME 挑战文件 |
| `certbot-logs` | `/var/log/letsencrypt` | certbot | 日志 |
| `./nginx/ssl.d` | `/etc/nginx/ssl.d` | nginx 只读 | HTTPS server 块 |
卷名在 `certbot/docker-compose.yml` 里用 `name:` 固定compose 默认会加项目名前缀,
两个 compose 项目目录不同,不固定会导致 nginx 侧 `external: true` 找不到卷)。
## 端口
| 服务 | 容器端口 | 宿主机端口 | 远程端口 |
|---|---|---|---|
| nginx | 80 | 8081 | 80 |
| nginx | 8443 | 8443 | 443 |
## 故障排查
```bash
# nginx 是否只加载了 HTTP 配置(冷启动预期如此)
docker exec nginx-proxy nginx -T | grep -c "listen 8443"
# ACME 验证路径是否可达(申请期间在容器内建个测试文件)
docker exec nginx-proxy sh -c 'echo probe > /var/www/certbot/.well-known/acme-challenge/test' 2>/dev/null || true
curl -i http://fastgpt.stonelan.cn/.well-known/acme-challenge/test
# 查看证书 / 续签日志
docker compose run --rm certbot certificates
tail -f /var/log/certbot-renew.log
```
## 注意事项
1. 域名 DNS 需解析到 frps 服务器,且 frpc 与 nginx 在同一 Docker 网络 `trim-default`
2. **不要**在命令里重复写 `certbot``docker compose run --rm certbot renew` 才是正确写法
(官方镜像 ENTRYPOINT 已经是 certbot写成 `run --rm certbot certbot renew` 会失败)
3. 证书有效期 90 天certbot 默认在剩余 30 天内才真正续签
4. `init-cert.sh` 使用 `--keep-until-expiring`,不要改成 `--force-renewal`
否则每次执行都重签,很容易撞 Let's Encrypt 的限流(同一组域名每周 5 张)