Certbot SSL证书维护

架构总览

┌──────────────────────────────────────────────────────────┐
│          Certbot SSL证书管理 · Docker三文件模式          │
├────────────┼─────────────────────────────────────────────┤
│  外部服务  │      Docker Host · ghost@oracle-linux       │
├────────────┼─────────────────────────────────────────────┤
│            │                                             │
│  name.com  │   ┌──────────┐ ┌──────────┐ ┌──────────┐    │
│  DNS API   │   │ add.yml  │ │renew.yml │ │ cli.yml  │    │
│            │   │ certonly │ │  renew   │ │sleep ∞  │    │
│            │   └────┬─────┘ └────┬─────┘ └────┬─────┘    │
│            │                                             │
│   Let's    │            └───────────┼────────────┘       │
│  Encrypt   │                     ┌─────┴─────┐           │
│    ACME    │                     │  certbot  │           │
│            │                     │   容器     │          │
│            │                     │  ←→ DNS   │           │
│            │                     │  ←→ ACME  │           │
│            │                     └─────┬─────┘           │
│            │                                             │
│            │         ┌──────────────┴─────────────┐      │
│            │         │   data/letsencrypt/        │      │
│            │         │   live/ghost.atibm.com/   │      │
│            │         │   scripts/   config/       │      │
│            │         └────────────────────────────┘      │
│            │                                             │
│            │                      ┌──────────┐           │
│            │                      │  nginx   │           │
│            │                      │  容器     │          │
│            │                      │ 读取证书  │          │
│            │                      └──────────┘           │
│            │                                             │
├────────────┼─────────────────────────────────────────────┤
│    Cron    │         02 08 * * * cd /www/certbot         │
│   宿主机   │       docker-compose -f renew.yml up        │
│            │      docker exec nginx nginx -s reload      │
└──────────────────────────────────────────────────────────┘

使用 certbot 容器 + name.com DNS API 自动化插件,通过 Docker Compose 管理 SSL 证书的完整生命周期。

工作目录:/www/certbot/(宿主机 ghost 用户)

核心设计:3 个独立的 docker-compose 文件,通过不同的 entrypoint 实现不同操作,共用同一个持久化数据卷 ./data/letsencrypt

目录结构

/www/certbot/
├── docker-compose.yml   # 主文件,当前 = renew 模式(certbot renew)
├── renew.yml            # 续期模式(certbot renew)
├── add.yml              # 新增/重置证书模式(certbot certonly --manual ...)
├── cli.yml              # 交互调试模式(sleep infinity)
├── data/
│   ├── letsencrypt/     # 证书持久化数据 ← 核心资产
│   ├── scripts/
│   │   └── certbot-dns-name-com/
│   │       └── src/certbot-dns-namecom.py   # name.com DNS API hook
│   ├── config/
│   │   └── certbot-dns-namecom.config.json  # name.com 账号+token(chmod 600)
│   └── log/             # certbot 日志

3个Docker-Compose文件详解

三个文件共享相同的 volumes 和 network 配置,唯一区别是 entrypoint 不同。

① renew.yml — 日常续期

entrypoint: /bin/sh -c "apk add --no-cache py3-requests > /dev/null && certbot renew"

  • 用途:续期所有已存在的证书,同时也是docker-compse.yml 主配置
  • 行为:certbot renew 检查所有已签发证书,仅对到期前30天内的证书续期
  • 使用场景:cron 定时任务、手动快速续期
  • 维护域名:11个(全部域名,含 v1008081~8084 和 git)

② add.yml — 新增/重置证书

services:
  certbot:
    container_name: "certbot"
    image: certbot/certbot:v3.1.0
    restart: "no"  # 不需要常驻
    volumes:
      - ./data/letsencrypt:/etc/letsencrypt
      - ./data/scripts/certbot-dns-name-com/src/certbot-dns-namecom.py:/usr/bin/certbot-dns-namecom.py:ro
      - ./data/config/certbot-dns-namecom.config.json:/etc/certbot-dns-namecom.config.json:ro
      - ./data/log:/var/log
    networks:
      - ghost_net
    # 启动后重置所维护的证书,完成后保持运行,供用户确认
    entrypoint: >
      /bin/sh -c "apk add --no-cache py3-requests > /dev/null &&
      certbot certonly --manual --preferred-challenges dns
      --manual-auth-hook '/usr/bin/certbot-dns-namecom.py add'
      --manual-cleanup-hook '/usr/bin/certbot-dns-namecom.py clean'
      --cert-name ghost.atibm.com
      --force-renewal
      --non-interactive
      --agree-tos
      --email abc@gmail.com
      -d a.atibm.com
      -d b.atibm.com
      -d c.atibm.com
      "
    deploy:
      resources:
        limits:
          memory: 32M
networks:
  ghost_net:
    external: true
  • 用途:首次申请证书、增删域名后重新签发、强制刷新证书
  • 行为:通过 name.com API 自动添加/清理 DNS TXT 记录完成域名验证,强制重新签发
  • 与 renew 的区别:renew 不能增删域名;add 用 --force-renewal 全部重签
  • 域名少的原因:只包含 8 个核心域名。如需增减域名编辑此文件

③ cli.yml — 交互调试

entrypoint: /bin/sh -c "apk add --no-cache py3-requests curl && sleep infinity"
  • 用途:进入容器交互式调试、手动执行 certbot 命令
  • 使用方式docker-compose -f cli.yml up -d 启动 → docker exec -it certbot sh 进入
  • 额外装了 curl:比另外两个文件多一个 curl,方便调试网络
  • 用完记得 downdocker-compose -f cli.yml down

生命周期操作

查看证书状态与域名清单

# 查看所有已签发证书(含证书名、域名清单、到期时间)
docker-compose run --rm --entrypoint "sh" certbot -c "certbot certificates"

# 查看证书中的 SAN 域名清单(Subject Alternative Names)
docker-compose run --rm --entrypoint "sh" certbot -c "openssl x509 -in /etc/letsencrypt/live/ghost.atibm.com/fullchain.pem -noout -ext subjectAltName"

# 查看证书文件(live 链接指向最新版本)
docker-compose run --rm --entrypoint "ls" certbot -la /etc/letsencrypt/live/ghost.atibm.com/

# 查看证书有效期
docker-compose run --rm --entrypoint "sh" certbot -c "openssl x509 -in /etc/letsencrypt/live/ghost.atibm.com/fullchain.pem -noout -dates"

首次申请证书

仅需执行一次。之后用 renew 续期即可。

cd /www/certbot
docker-compose -f add.yml up

执行后容器自动退出。证书保存到 ./data/letsencrypt/live/ghost.atibm.com/

日常续期(手动)

cd /www/certbot
docker-compose -f renew.yml up

certbot renew 只续期距到期不足30天的证书,没到期的不会操作。

增删域名

  1. 编辑 add.yml,在 entrypoint 中增加/删除 -d yourdomain.atibm.com
  2. 执行:docker-compose -f add.yml up

注意:使用 --force-renewal 会重新签发,letsencrypt 有速率限制(每周5次/域名)。频繁操作会被限流。

删除证书

docker-compose run --rm --entrypoint "sh" certbot -c "certbot delete --cert-name ghost.atibm.com"

删除后需重新用 add.yml 申请。

Nginx 重载证书

证书更新后 nginx 不会自动加载新证书,需要 reload:

docker exec nginx nginx -s reload

renew.yml 和 add.yml 不自动做这一步(容器执行完就退出了),需手动执行或在 cron 脚本中串联。

Cron 自动化

宿主机 crontab 每日执行证书续期,并自动 reload nginx。

当前 cron 配置

# 每周日凌晨 3 点自动续期 SSL 证书,并重启 Nginx 容器加载新证书
0 2 * * 0 cd /www/certbot && { echo "$(date) - [Start]" && docker-compose up certbot && echo "$(date) - [Nginx Reloading]" && docker exec nginx nginx -s reload && echo "$(date) - [Done]"; } >> /home/ghost/cron_certbot.log 2>&1

Cron 管理

crontab -l                 # 查看任务
crontab -e                 # 编辑
systemctl status crond     # 检查 cron 服务状态
systemctl restart crond    # 重启 cron 服务

日志查看

cat /home/ghost/cron_certbot.log   # 每次执行追加的记录

依赖资源

  • name.com API 脚本git clone --depth 1 https://github.com/laonan/certbot-dns-name-com.git data/scripts/certbot-dns-name-com
  • name.com Token 配置data/config/certbot-dns-namecom.config.json(chmod 600)
  • API 白名单:服务器 IP 需加入 name.com API 白名单

调试技巧

# SSL 握手检查
curl -v https://4060.atibm.com 2>&1 | grep -E "SSL|certificate|subject"

# 证书链检查
openssl s_client -connect 4060.atibm.com:443 -servername 4060.atibm.com

# name.com API 连通性测试
python3 -c "import requests; r = requests.get('https://api.name.com/v4/domains/atibm.com', auth=('USERNAME', 'TOKEN')); print(r.status_code); print(r.json())"

# 模拟申请(dry-run,不实际签发)
docker-compose -f cli.yml up -d
docker exec certbot certbot certonly --manual --preferred-challenges dns \
  --manual-auth-hook '/usr/bin/certbot-dns-namecom.py add' \
  --manual-cleanup-hook '/usr/bin/certbot-dns-namecom.py clean' \
  --cert-name ghost.atibm.com -d test.atibm.com \
  --agree-tos --email cheanty@gmail.com --non-interactive \
  --server https://acme-v02.api.letsencrypt.org/directory --dry-run
docker-compose -f cli.yml down

注意事项

  • user 配置已移除:之前设过 user: "1000:1000" 导致权限问题,当前配置已去掉,以 root 运行容器
  • 容器 memory 限制 32M:certbot 很轻量,足够
  • restart: no:容器做完事就退,不需要保持运行(cli.yml 除外)
  • renew vs add:日常只用 renew.yml;只有改域名清单时才用 add.yml
  • docker-compose.yml 内容与 renew.yml 相同:是默认的快捷方式。可以删掉 docker-compose.yml,全部用 -f 指定文件
  • Nginx reload 要手动做:cron 脚本中串联了 docker exec nginx nginx -s reload,手动执行续期时别忘了
  • Let's Encrypt 速率限制:每周每域名 5 次证书签发。--force-renewal 会计入,不要频繁执行 add.yml
  • 证书路径:nginx 通过 ghost_net 网络共享,nginx 容器中也是用 /etc/letsencrypt 吗?需要确认 nginx 是否也映射了同一个 letsencrypt 目录