报错现象
在申请或部署 SSL 证书时,常见几类报错:
1. ACME 客户端申请阶段:
```
Challenge failed for domain example.com
Timeout during connect (likely firewall problem)
Invalid response from http://example.com/.well-known/acme-challenge/xxx: 404
DNS problem: NXDOMAIN looking up A for example.com
Problem binding to port 80: Could not bind to IPv4 or IPv6
Too many failed authorizations recently
```
出现场景:运行 certbot、acme.sh 等客户端,选择 HTTP-01 或 DNS-01 验证后。
2. 浏览器访问阶段:
```
NET::ERR_CERT_COMMON_NAME_INVALID
NET::ERR_CERT_AUTHORITY_INVALID
您的连接不是私密连接
证书链不完整
```
出现场景:Nginx/Apache 配置了证书,但浏览器不信任,或部分客户端(Java、旧 Android、小程序)请求失败。
3. 自动续期失败:
cron 或 systemd timer 执行 certbot renew 后证书未更新,日志里出现 Attempting to renew cert ... produced an unexpected error。
影响范围:网站被标记不安全、API 调用中断、移动端 WebView 无法加载、支付回调失败等。排查时先区分是“申请失败”还是“申请成功但不生效”。
可能原因
按出现概率从高到低:
1. 域名解析问题:A/AAAA 记录缺失、指向错误 IP、存在冲突的 CNAME、DNS 缓存未刷新、只有 A 记录但客户端走 IPv6 导致验证超时。
2. 验证方式与服务器实际配置不匹配:HTTP-01 要求 80 端口可从公网访问并返回指定内容;DNS-01 要求 TXT 记录正确传播;TLS-ALPN-01 要求 443 端口直达本机。
3. 80 端口被占用或防火墙/安全组未放行:Nginx、Apache、Docker 容器占用 80;云安全组、ufw、iptables 拦截。
4. 证书链不完整或部署文件用错:只部署了域名证书,缺少中间 CA;Nginx 把 ssl_certificate 指向 cert.pem 而不是 fullchain.pem。
5. 自动续期配置失效:timer/cron 未启用、webroot 路径变化、DNS API 凭证过期、deploy hook 未重载服务、权限不足。
6. 触达 CA 速率限制或 CAA 记录限制:短时间内重复申请、CAA 只允许特定 CA。
7. 服务器时间偏差过大、SNI 配置错误、HTTP 强制跳转 HTTPS 干扰验证、CDN/WAF 回源拦截。
逐条排查与解决
1. 检查域名解析
先确认域名指向当前服务器的公网 IP。
```
dig +short A example.com
dig +short AAAA example.com
dig +short CNAME example.com
curl -4 ifconfig.me
curl -6 ifconfig.me
```
判断:A 记录应返回服务器公网 IPv4。如果返回 203.0.113.10 这类文档示例 IP,说明解析未生效或配错。若有 AAAA 记录但服务器没有 IPv6 地址,部分 CA 会优先走 IPv6 验证导致超时。可暂时删除 AAAA 记录,或给服务器配置 IPv6 并放行 80/443。
修改 DNS 后等待 TTL 过期,用公共解析器验证:
```
dig @8.8.8.8 +short A example.com
dig @1.1.1.1 +short A example.com
```
如果多个解析器结果不一致,说明传播未完成,等一段时间再申请。
2. 确认验证方式
HTTP-01 验证:CA 会访问 http://example.com/.well-known/acme-challenge/随机字符串。需要 80 端口开放,且 Web 服务器能返回文件内容。
用 webroot 模式示例:
```
sudo certbot certonly --webroot -w /var/www/html -d example.com
```
Nginx 中确保验证路径不被重写、不被跳转:
```
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
allow all;
}
```
手动放一个测试文件:
```
echo "hello-acme" | sudo tee /var/www/html/.well-known/acme-challenge/hello
curl -v http://example.com/.well-known/acme-challenge/hello
```
判断:返回 200 且内容为 hello-acme。若返回 301 到 HTTPS,需要让 HTTP 验证路径可访问,或改用 DNS-01。若返回 404,检查 root 路径和 location 是否匹配。若超时,回到第 3 条查端口和防火墙。
DNS-01 验证:CA 会查询 _acme-challenge.example.com 的 TXT 记录。
```
dig +short TXT _acme-challenge.example.com
dig @8.8.8.8 +short TXT _acme-challenge.example.com
```
判断:返回的 TXT 值应与客户端要求的一致。使用 DNS API 插件时,检查 API 凭证是否有权限修改 TXT 记录。若手动添加 TXT,注意不要多加引号或换行。
TLS-ALPN-01 验证:需要 443 端口直接到达本机,且没有 CDN 终止 TLS。判断:openssl s_client -connect example.com:443 -servername example.com 返回的证书是否为本机证书。
3. 检查 80 端口占用与防火墙
```
sudo ss -tlnp | grep ':80'
sudo lsof -i :80
sudo ss -tlnp | grep ':443'
```
如果 80 被 Nginx 占用,不要用 --standalone 硬抢端口。两种做法:
- 用 webroot 模式,让 Nginx 继续监听 80。
- 临时停掉 Nginx:
```
sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com
sudo systemctl start nginx
```
如果使用 Docker,检查端口映射:
```
docker ps --format '{{.Names}} {{.Ports}}'
```
防火墙检查:
```
sudo ufw status
sudo iptables -L -n
sudo firewall-cmd --list-all
```
云服务器还要在控制台安全组放行 80 和 443。判断:从外网执行 curl -v http://example.com,若连接超时,通常是安全组或防火墙拦截。
4. 检查证书链与部署文件
申请成功后,证书目录通常包含:
cert.pem:域名证书chain.pem:中间 CA 证书fullchain.pem:域名证书 + 中间 CAprivkey.pem:私钥
Nginx 应这样配置:
```
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}
```
Apache 应这样配置:
```
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
```
检查实际链:
```
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
```
判断:输出中应有多个证书,末尾是根 CA 或中间 CA。如果只有一个证书,说明链不完整。再用:
```
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/letsencrypt/live/example.com/fullchain.pem
```
返回 OK 表示链文件完整。浏览器正常但 Java 或旧 Android 报错,通常是链不完整或顺序错误。
5. 检查自动续期
查看定时任务是否启用:
```
systemctl list-timers | grep certbot
sudo crontab -l
```
执行演练:
```
sudo certbot renew --dry-run
```
查看日志:
```
sudo journalctl -u certbot.timer
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log
```
常见问题与处理:
- webroot 路径变化:修改续期配置中的
--webroot-path。 - DNS API 凭证过期:更新凭证文件或环境变量。
- 续期后服务未重载:加 deploy hook。
```
sudo certbot renew --deploy-hook "systemctl reload nginx"
```
手动强制续期测试(不要频繁执行,避免触发速率限制):
```
sudo certbot renew --force-renewal
```
判断:--dry-run 成功说明续期流程基本可用;若失败,按日志里的 challenge 类型回到第 1、2、3 条排查。
6. 检查速率限制与 CAA
```
dig +short CAA example.com
```
如果返回 0 issue "某个CA",而申请的不是该 CA,就会失败。需要修改 CAA 记录或改用允许的 CA。若报错包含 too many certificates already issued、too many failed authorizations,说明触发了 CA 的速率限制。等待限制窗口重置,或改用其他验证方式。具体限制以 CA 官方页面为准。
7. 检查服务器时间与 SNI
```
date -u
timedatectl status
```
时间偏差过大会导致 ACME 签名验证失败。启用 NTP:
```
sudo timedatectl set-ntp true
```
多域名或 SNI 场景,确认请求的域名匹配到正确 server 块:
```
openssl s_client -connect 127.0.0.1:443 -servername example.com </dev/null | openssl x509 -noout -subject -dates
```
判断:subject 中的 CN 或 SAN 应包含 example.com。若不包含,说明 Nginx/Apache 用了默认证书或错误证书。
都不管用时的兜底方案
1. 换验证方式。HTTP-01 反复失败时,改用 DNS-01;DNS-01 传播慢时,改用 HTTP-01。两种方式交叉验证,能快速定位是解析问题还是 Web 服务问题。
2. 换一台机器申请。在一台 80/443 干净的临时服务器上申请,成功后把 fullchain.pem 和 privkey.pem 拷贝到目标服务器。注意私钥权限设为 600。
3. 使用手动模式。certbot certonly --manual 可以按提示手动添加 DNS TXT 记录,适合排查阶段。但手动模式默认不能自动续期,需要后续补自动化。
4. 换 ACME 客户端。不同客户端对验证流程、DNS API 支持不同,具体用法以官方文档当前版本为准。
5. 抓包确认请求是否到达。在服务器上执行:
```
sudo tcpdump -i any -n port 80 or port 443
```
然后重新申请,观察是否有来自 CA 的请求。若没有请求到达,问题在 DNS、防火墙或 CDN;若有请求但返回错误,问题在 Web 服务器配置。
6. 检查 CDN/WAF。如果域名接入了 CDN,确认回源是否放行 .well-known/acme-challenge/ 路径,或者临时把解析切到源站再申请。
7. 查看完整日志。ACME 客户端日志通常比屏幕输出更详细,重点看 challenge 类型、请求 URL、返回状态码。若仍无法判断,带上日志和域名信息联系 CA 支持。
如何预防再次发生
1. 域名解析保持稳定。给需要证书的域名固定 A 记录,A/AAAA 保持一致。变更解析前先确认新 IP 已能提供服务,TTL 不要设得过长。
2. 优先选适合自己环境的验证方式。服务器 80 端口稳定可达,用 HTTP-01;域名多、服务器分散、不想暴露 80,用 DNS-01。DNS API 凭证要最小权限并妥善保管。
3. 变更防火墙和安全组前先自检。用 curl 从外网访问验证路径,确认 80/443 可达。云安全组、主机防火墙、容器端口映射三处都要检查。
4. 始终部署 fullchain。Nginx 用 fullchain.pem,Apache 用 fullchain.pem。定期用 openssl s_client 检查证书链是否完整。
5. 自动续期要演练。配置 systemd timer 或 cron 后,执行 certbot renew --dry-run。加 deploy hook 自动重载 Nginx/Apache,避免证书更新了但服务仍用旧证书。
6. 设置到期提醒。即使有自动续期,也用监控工具检查证书剩余有效期。到期前 30 天、7 天分别告警。
7. 保留排查记录。记录域名、验证方式、使用的 ACME 客户端、证书路径、续期命令。下次出现类似报错时,能快速对照。
按照上面的顺序排查,多数 SSL 证书申请失败或不生效的问题都能定位。核心思路是:先确认域名解析到达正确的服务器,再确认验证方式对应的端口和路径可达,然后检查证书链和续期配置,再考虑 CA 限制和 CDN 等外部因素。
