第 7 章:Let's Encrypt
第 7 章:Let’s Encrypt
Let’s Encrypt 是互联网安全的里程碑项目,通过 ACME 协议实现了免费、自动化、开放的证书签发。本章全面介绍 Let’s Encrypt 的工作原理和自动化实践。
7.1 Let’s Encrypt 简介
基本信息
| 项目 | 说明 |
|---|---|
| 运营方 | Internet Security Research Group (ISRG) |
| 成立时间 | 2015 年 |
| 证书类型 | DV(Domain Validation)证书 |
| 费用 | 完全免费 |
| 有效期 | 90 天(鼓励自动化续期) |
| 根 CA | ISRG Root X1 |
| 服务域名 | 通过 HTTPS 接入 |
| 证书市场份额 | 超过 30%(截至 2025 年) |
信任链
ISRG Root X1 (自签名,预装在各平台信任存储中)
└── R3 (中间 CA)
└── 终端证书 (*.example.com)
└── R4 (中间 CA,备用)
└── 终端证书
旧设备兼容链(通过 DST Root CA X3 交叉签名):
DST Root CA X3 (已过期 2021-09-30)
└── ISRG Root X1
└── R3
└── 终端证书
💡 提示:ISRG Root X1 已被所有主流操作系统和浏览器信任。对于不信任 ISRG Root X1 的旧设备(Android 7.1 以下),可以使用 Let’s Encrypt 提供的交叉签名链。
7.2 ACME 协议
ACME(Automatic Certificate Management Environment)是 Let’s Encrypt 使用的自动化证书管理协议,定义在 RFC 8555 中。
ACME 工作流程
1. 客户端生成密钥对和账户注册
Client ──register──▶ ACME Server
(创建账户,同意服务条款)
2. 客户端请求证书
Client ──new-order──▶ ACME Server
(指定需要的域名)
3. 服务器返回验证挑战
ACME Server ──challenges──▶ Client
(HTTP-01, DNS-01, TLS-ALPN-01)
4. 客户端完成验证
Client ──完成挑战──▶ ACME Server
(放置验证文件/添加 DNS 记录)
5. 服务器验证通过后签发证书
ACME Server ──certificate──▶ Client
(返回签发的证书)
ACME 验证方式
| 方式 | 原理 | 适用场景 | 操作 |
|---|---|---|---|
| HTTP-01 | 在 /.well-known/acme-challenge/ 放置文件 | 有 Web 服务器 | 自动(certbot) |
| DNS-01 | 添加 _acme-challenge TXT 记录 | 通配符证书、无 Web 服务器 | 需要 DNS API |
| TLS-ALPN-01 | 通过 TLS 握手验证 | 只有 443 端口 | 需要专用工具 |
HTTP-01 验证详解
验证过程:
1. ACME 服务器向客户端发送 token
2. 客户端在 Web 服务器上放置文件:
http://example.com/.well-known/acme-challenge/<token>
文件内容: <token>.<thumbprint-of-account-key>
3. ACME 服务器从 80 端口访问该 URL
4. 验证文件内容正确 → 验证通过
# 手动模拟 HTTP-01 验证
# 创建验证目录
sudo mkdir -p /var/www/html/.well-known/acme-challenge/
echo "test-token.test-thumbprint" | sudo tee /var/www/html/.well-known/acme-challenge/test-token
curl http://example.com/.well-known/acme-challenge/test-token
DNS-01 验证详解
验证过程:
1. ACME 服务器向客户端发送 token
2. 客户端添加 DNS TXT 记录:
_acme-challenge.example.com TXT "<token>.<thumbprint>"
3. ACME 服务器查询该 TXT 记录
4. 验证记录正确 → 验证通过
# 查看 DNS-01 验证记录
dig _acme-challenge.example.com TXT
7.3 使用 certbot
安装 certbot
# Debian/Ubuntu
sudo apt install certbot
# 或使用 snap 安装(推荐)
sudo snap install --classic certbot
# RHEL/CentOS/Fedora
sudo dnf install certbot
# 验证安装
certbot --version
基本使用
# Nginx 插件(自动配置)
sudo certbot --nginx -d example.com -d www.example.com
# Apache 插件
sudo certbot --apache -d example.com -d www.example.com
# Standalone 模式(临时启动 Web 服务器)
sudo certbot certonly --standalone -d example.com
# Webroot 模式(使用现有 Web 服务器)
sudo certbot certonly --webroot -w /var/www/html -d example.com
# 使用 DNS 验证(通配符证书需要)
sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d example.com
通配符证书
# 通配符证书必须使用 DNS-01 验证
sudo certbot certonly \
--manual \
--preferred-challenges dns \
-d "*.example.com" \
-d "example.com"
# 输出:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Please deploy a DNS TXT record under the name:
#
# _acme-challenge.example.com
#
# with the following value:
#
# abc123xyz789...
#
# Before continuing, verify the TXT record has been deployed.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
DNS 插件自动化
# 使用 Cloudflare DNS 插件
sudo apt install python3-certbot-dns-cloudflare
# 创建凭证文件
cat > /etc/letsencrypt/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = your-api-token
EOF
chmod 600 /etc/letsencrypt/cloudflare.ini
# 自动签发通配符证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d "*.example.com" \
-d "example.com"
# 支持的 DNS 插件
# python3-certbot-dns-cloudflare (Cloudflare)
# python3-certbot-dns-route53 (AWS Route 53)
# python3-certbot-dns-google (Google Cloud DNS)
# python3-certbot-dns-digitalocean (DigitalOcean)
# python3-certbot-dns-dnspod (DNSPod)
7.4 证书续期
自动续期
# 测试续期
sudo certbot renew --dry-run
# 查看已安装的证书
sudo certbot certificates
# 手动续期
sudo certbot renew
# 设置自动续期(systemd timer,certbot 安装时自动配置)
sudo systemctl status certbot.timer
# 查看定时器详情
sudo systemctl list-timers certbot.timer
# 手动设置 cron 续期
# certbot 建议一天运行两次
cat > /etc/cron.d/certbot << 'EOF'
# 每 12 小时尝试续期
0 */12 * * * root certbot renew --quiet --deploy-hook "systemctl reload nginx" 2>&1
EOF
续期钩子
# 续期前钩子(停止服务释放 80 端口)
sudo certbot renew --pre-hook "systemctl stop nginx"
# 续期后钩子(重新加载服务)
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 配置默认钩子
cat >> /etc/letsencrypt/cli.ini << 'EOF'
deploy-hook = systemctl reload nginx
pre-hook = systemctl stop nginx
post-hook = systemctl start nginx
EOF
续期最佳实践
续期时间线
────────────────────────────────────────────▶
│ │ │ │
签发 60 天 80 天 90 天
│ │ │
│ certbot renew │ 过期
│ (建议在此区间续期) │
│ │
│ ▼
│ 最迟续期时间
| 策略 | 说明 |
|---|---|
| 推荐间隔 | 每 12 小时运行 certbot renew |
| dry-run | 首次配置后务必执行 --dry-run |
| 监控 | 监控证书过期时间,确保续期成功 |
| 降级策略 | 如果自动续期失败,手动续期流程 |
7.5 Nginx / Apache 集成
Nginx 完整配置
server {
listen 80;
server_name example.com www.example.com;
# ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# HTTP → HTTPS 重定向
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# Let's Encrypt 证书路径
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 优化
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# Session 复用
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
root /var/www/html;
index index.html;
}
Apache 配置
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
# ACME challenge
Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/
# HTTP → HTTPS 重定向
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# HSTS
Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>
7.6 速率限制
Let’s Encrypt 有严格的速率限制,了解它们可以避免被封禁。
主要限制
| 限制类型 | 限制值 | 说明 |
|---|---|---|
| 每个域名每周证书数 | 50 张 | 包括所有子域名 |
| 每个 IP 每 3 小时订单数 | 10 个 | 新订单请求 |
| 每个域名每小时验证失败 | 5 次 | 验证失败计数 |
| 重复证书 | 5 张/周 | 相同域名组合的证书 |
| 续期不限制 | - | 续期不计入速率限制 |
# 查看当前域名的证书数量
curl -s "https://crt.sh/?q=example.com&output=json" | jq 'length'
# 测试验证(不会消耗速率限制)
sudo certbot renew --dry-run
⚠️ 注意:在开发和测试环境中,务必使用 Let’s Encrypt 的 staging 环境,避免消耗生产环境的速率限制。
# 使用 staging 环境
sudo certbot certonly --staging --standalone -d test.example.com
# 清除 staging 证书
sudo certbot delete --cert-name test.example.com
# Nginx 配置中使用 staging 证书
# ssl_certificate /etc/letsencrypt/live/test.example.com/fullchain.pem; (staging 路径)
7.7 证书透明度(CT)
Let’s Encrypt 自动将所有签发的证书提交到 CT 日志。
查看 CT 记录
# 使用 crt.sh 查询
curl -s "https://crt.sh/?q=example.com&output=json&exclude=expired" | \
jq '[.[] | select(.issuer_name_id > 0)] |
group_by(.issuer_name) |
map({issuer: .[0].issuer_name, count: length})'
# 查看 Let's Encrypt 签发的证书
curl -s "https://crt.sh/?q=example.com&issuer_name_id=183267&output=json" | \
jq '.[] | {id, not_before, not_after, name_value}'
CT 监控脚本
#!/usr/bin/env bash
# ct-monitor-le.sh - 监控 Let's Encrypt 为你的域名签发的证书
DOMAIN="${1:?用法: $0 <domain>}"
echo "检查 ${DOMAIN} 的 Let's Encrypt CT 记录..."
echo ""
# Let's Encrypt 的 issuer_name_id (crt.sh)
# R3: 183267, R10: 3334554, R11: 3334555
LE_CERTS=$(curl -s "https://crt.sh/?q=${DOMAIN}&output=json" 2>/dev/null | \
jq '[.[] | select(.issuer_name | contains("Let'"'"'s Encrypt") or contains("ISRG"))]')
COUNT=$(echo "$LE_CERTS" | jq 'length')
echo "共找到 ${COUNT} 张 Let's Encrypt 证书"
echo ""
echo "$LE_CERTS" | jq -r '.[-5:] | .[] |
" ID: \(.id)\n 域名: \(.name_value)\n 签发: \(.not_before)\n 过期: \(.not_after)\n"'
7.8 替代客户端
certbot 替代方案
| 工具 | 语言 | 特点 |
|---|---|---|
| certbot | Python | 官方推荐,插件丰富 |
| acme.sh | Shell | 纯 Shell 实现,轻量 |
| lego | Go | 库/CLI,DNS 插件丰富 |
| Certify The Web | Windows | GUI 工具,适合 Windows 管理 |
acme.sh 使用
# 安装
curl https://get.acme.sh | sh
# 签发证书(standalone 模式)
acme.sh --issue -d example.com --standalone
# 使用 webroot 模式
acme.sh --issue -d example.com -w /var/www/html
# 通配符证书(Cloudflare DNS)
acme.sh --issue -d "*.example.com" --dns dns_cf
# 安装到 Nginx
acme.sh --install-cert -d example.com \
--key-file /etc/nginx/ssl/key.pem \
--fullchain-file /etc/nginx/ssl/fullchain.pem \
--reloadcmd "systemctl reload nginx"
# 自动续期(acme.sh 安装时自动设置 cron)
crontab -l | grep acme
# 0 0 * * * "/home/user/.acme.sh/acme.sh" --cron
lego 使用
# 安装
go install github.com/go-acme/lego/v4/cmd/lego@latest
# HTTP 验证
lego --http --domains example.com --email [email protected] run
# DNS 验证(Cloudflare)
CLOUDFLARE_EMAIL=[email protected] \
CLOUDFLARE_API_KEY=xxx \
lego --dns cloudflare --domains "*.example.com" --email [email protected] run
# 证书存储路径
ls .lego/certs/example.com/
7.9 高级场景
多域名证书
# certbot 多域名
sudo certbot certonly --nginx \
-d example.com -d www.example.com \
-d api.example.com -d admin.example.com
# 查看证书包含的域名
sudo certbot certificates
泛域名 + 裸域名
# 同时申请泛域名和裸域名
sudo certbot certonly \
--manual --preferred-challenges dns \
-d "example.com" \
-d "*.example.com"
# 注意:需要为两条 DNS 记录添加 TXT 值
# _acme-challenge.example.com (两条 TXT 记录)
Docker 环境
# docker-compose.yml
version: '3'
services:
nginx:
image: nginx:latest
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- web-root:/var/www/html
certbot:
image: certbot/certbot
volumes:
- certbot-etc:/etc/letsencrypt
- certbot-var:/var/lib/letsencrypt
- web-root:/var/www/html
depends_on:
- nginx
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
volumes:
certbot-etc:
certbot-var:
web-root:
7.10 本章小结
| 主题 | 关键要点 |
|---|---|
| Let’s Encrypt | 免费 DV 证书,90 天有效期 |
| ACME 协议 | HTTP-01 / DNS-01 / TLS-ALPN-01 三种验证方式 |
| certbot | 官方推荐客户端,支持 Nginx/Apache 插件 |
| 通配符证书 | 必须使用 DNS-01 验证 |
| 续期 | 推荐每 12 小时自动续期 |
| 速率限制 | 开发测试使用 staging 环境 |
| CT | Let’s Encrypt 自动提交到 CT 日志 |
📚 执展阅读
上一章:第 6 章:OpenSSL 工具 下一章:第 8 章:搭建私有 CA — 学习使用 CFSSL、easy-rsa 等工具搭建私有 CA。