申 Let's Encrypt 通配符证书(DNS-01 + Cloudflare API)

起因

要给 *.example.com 申通配符证书(一个证书管所有子域名)。
普通 --nginx / --apache certbot 用 HTTP-01 challenge,
不支持通配符 —— LE 强制通配符必须用 DNS-01 challenge。

DNS-01:在 DNS 加一条 _acme-challenge.example.com TXT 记录,
LE 来查这条 → 验证你控制这个域。

手动加 TXT 记录烦 + 续期时还要再加 → 自动化 DNS API 是必经之路。

用 certbot + Cloudflare API

1. 拿 Cloudflare API token

Cloudflare 控制台 → 我的个人资料 → API 令牌 → 创建令牌:

权限:
- Zone:Zone:Read
- Zone:DNS:Edit

资源:包括 → 特定区域 → example.com

复制 token。

2. 装 certbot + cloudflare 插件

sudo apt install -y certbot python3-certbot-dns-cloudflare

3. 配置 token 文件

sudo mkdir -p /etc/letsencrypt
sudo nano /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = your-token-here
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

4. 申请通配符证书

sudo certbot certonly \
    --dns-cloudflare \
    --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
    --dns-cloudflare-propagation-seconds 30 \
    -d '*.example.com' \
    -d 'example.com' \
    --agree-tos --no-eff-email \
    -m [email protected]

要点:

  • -d '*.example.com' -d 'example.com':通配符 + 裸域名要分别 list
    (通配符不 cover 裸域名 + apex domain)
  • --dns-cloudflare-propagation-seconds 30:让 LE 查询前等 30 秒
    让 DNS 传播

成功输出:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem

5. nginx 配置

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name app.example.com api.example.com blog.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # 加强配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_dhparam /etc/ssl/dhparam.pem;   # openssl dhparam -out ... 4096

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    location / {
        proxy_pass http://localhost:8080;
    }
}
sudo nginx -t && sudo systemctl reload nginx

6. 自动续期

certbot 装好后默认 systemd timer:

sudo systemctl list-timers certbot.timer
# certbot.timer  active  next run in ~12h

每 12 小时检查证书;< 30 天到期时续。

确认续期能跑通:

sudo certbot renew --dry-run

成功输出说明真续期会 work。

7. 续期后 reload nginx

certbot hook:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx-reload <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload

证书续期成功后自动 reload nginx,新证书生效。

其它 DNS provider

certbot-dns-* 插件覆盖大多数:

sudo apt install -y \
    python3-certbot-dns-cloudflare \
    python3-certbot-dns-route53 \
    python3-certbot-dns-google \
    python3-certbot-dns-digitalocean \
    python3-certbot-dns-rfc2136     # 用于 bind 等支持 RFC2136 的服务器

完整列表 see certbot docs。

Route53 / AWS

sudo certbot certonly \
    --dns-route53 \
    -d '*.example.com' -d 'example.com'

凭据从 ~/.aws/credentials 或 instance role。

Google Cloud DNS

sudo certbot certonly \
    --dns-google \
    --dns-google-credentials /etc/letsencrypt/gcloud-svc.json \
    -d '*.example.com'

需要 service account JSON。

用 acme.sh(轻量替代)

curl https://get.acme.sh | sh
export CF_Token="..."
export CF_Zone_ID="..."
acme.sh --issue --dns dns_cf -d '*.example.com' -d 'example.com'

acme.sh 是 shell 写的,比 certbot 轻量 + 不依赖 Python。
内置上百个 DNS provider。功能等价。

Caddy 一行搞定

*.example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    reverse_proxy localhost:8080
}

sudo caddy add-package github.com/caddy-dns/cloudflare 装好插件后
这就够了。Caddy 自动 ACME + DNS-01 + 续期,零额外配置。

如果选 Caddy 作反代,完全不用 certbot。

DNS-01 vs HTTP-01

DNS-01 HTTP-01
通配符
需要 80 端口
DNS provider API
域名 ACME 验证 通过 DNS 通过 HTTP
内网 / 防火墙后服务器 ✅(DNS 在云端) ❌(需公网 80)
实施复杂度 简单

总结:

  • 普通单域名 + 公网 → HTTP-01
  • 通配符 / 内网 / 多服务器 → DNS-01

Let's Encrypt 限制

  • 每周每证书 5 次重复申请(同样 SAN 列表)
  • 每周 50 个不同 SAN 组合
  • 失败计数:连续 5 次 fail 后 1 小时锁
  • 全球每周每注册域名 300 张新证书

正常使用碰不到。测试时用 staging server 避免被限:

certbot ... --staging

或者用 ZeroSSL / BuyPass / Google CA 等其它 ACME provider 分散。

监控证书到期

# 看到期时间
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate
# notAfter=Jul 15 12:00:00 2024 GMT

Prometheus blackbox_exporter:

- name: tls_cert_not_after
  url: https://example.com
  threshold_days: 14   # < 14 天告警

监控让你看 certbot 是否真的在续。一次因 cron 没跑而过期 → 客户全报错
红色警告,痛。

踩过的坑

  1. 通配符不 cover 二级*.example.com 不包含 example.com 本身(apex),
    也不包含 *.sub.example.com。第二级通配符要单独申 *.api.example.com

  2. API token 权限不够:缺 DNS:Edit → 加 TXT 失败 → certbot 报
    "rate limit" 误导。先 dig _acme-challenge.example.com TXT 验证。

  3. DNS propagation 时间:cloudflare 一般 30s 内全球同步。
    AWS / DigitalOcean 偶尔慢,调 --propagation-seconds 60

  4. renew dry-run 成功 → 真 renew 失败:dry-run 用 staging API,
    生产可能 rate limit。看 journalctl -u certbot.service 真实日志。

  5. fullchain vs cert:nginx 用 fullchain.pem(含中间证书)。
    写成 cert.pem 浏览器报"untrusted intermediate"。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。