起因
要给 *.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 没跑而过期 → 客户全报错
红色警告,痛。
踩过的坑
-
通配符不 cover 二级:
*.example.com不包含example.com本身(apex),
也不包含*.sub.example.com。第二级通配符要单独申*.api.example.com。 -
API token 权限不够:缺 DNS:Edit → 加 TXT 失败 → certbot 报
"rate limit" 误导。先dig _acme-challenge.example.com TXT验证。 -
DNS propagation 时间:cloudflare 一般 30s 内全球同步。
AWS / DigitalOcean 偶尔慢,调--propagation-seconds 60。 -
renew dry-run 成功 → 真 renew 失败:dry-run 用 staging API,
生产可能 rate limit。看journalctl -u certbot.service真实日志。 -
fullchain vs cert:nginx 用
fullchain.pem(含中间证书)。
写成cert.pem浏览器报"untrusted intermediate"。
登录后参与评论。