证书签发和续期

unauthorized Invalid response 403:Certbot 验证被拒的修法

作者: · 最后核实

报错信息Type: unauthorized Detail: 203.0.113.10: Invalid response from http://example.com/.well-known/acme-challenge/...: 403

这个报错的意思是:Let's Encrypt 连上了你的 Web 服务器 80 端口,请求 /.well-known/acme-challenge/ 下的验证文件,得到的却是 403 Forbidden。 Web 服务器内部或前面有东西拒绝了这个路径:禁止访问点开头文件的规则、WAF 或 CDN、文件权限。

快速修复

修法是给 /.well-known/acme-challenge/ 加一个优先于其他规则的放行例外。先从外部网络模拟一次请求:

sudo mkdir -p /var/www/html/.well-known/acme-challenge
echo ok | sudo tee /var/www/html/.well-known/acme-challenge/test
curl -i http://example.com/.well-known/acme-challenge/test

拿到 403 就在其他规则之前加例外;curl 超时而不是 403,见 Timeout during connect。Nginx 写法:

location ^~ /.well-known/acme-challenge/ {
    root /var/www/html;
    default_type "text/plain";
    allow all;
}

重载(sudo nginx -t && sudo systemctl reload nginx),确认 curl 返回 ok,再执行 sudo certbot renew --dry-run。

原因

403 有 5 种来源,最常见的是禁止访问点开头路径的规则把 /.well-known/ 也拦了。

1. 有规则禁止访问隐藏路径

很多安全加固配置里有 location ~ /\. { deny all; }(Nginx),或者 <FilesMatch "^\.">、RedirectMatch 403 /\.(Apache),它们会把 /.well-known/ 一起拦掉。

  • 确认: 上面的 curl 返回的 403 来自你的服务器(看 Server: 响应头),并且 nginx -T 或 Apache 配置里能找到这条规则。
  • 修复: 加上面那段 ^~ /.well-known/acme-challenge/(^~ 能阻止正则 location 再匹配)。Apache 写法:
<Location "/.well-known/acme-challenge/">
    Require all granted
</Location>

2. WAF、CDN 或安全插件拦截

机器人防护、地域封禁、部分网站程序的安全插件,会对陌生客户端返回 403。Let's Encrypt 从多个境外网络位置验证,任何一个被拦都可能导致失败。

  • 确认: 403 页面或 Server: 头来自 CDN 或 WAF,或者插件日志里有拦截记录。
  • 修复: 加一条规则,对 /.well-known/acme-challenge/* 跳过防护;或改用 DNS-01。

3. 文件权限或 SELinux

  • 确认: Web 服务器错误日志里有验证文件的 Permission denied;CentOS/Rocky 上 ls -Z 显示标签不是 httpd_sys_content_t。
  • 修复: 目录 chmod 755、文件 644,让 Web 服务器用户能读到;执行 sudo restorecon -Rv /var/www。

4. HTTPS 那边或应用程序返回 403

80 端口跳转到了 HTTPS,而 HTTPS 的 server 块或应用(框架路由、登录校验中间件)拒绝了这个路径。

  • 确认: curl -iL 显示先 301 到 HTTPS,然后 403。
  • 修复: 在 HTTPS 的 server 块里也加同样的例外,或者在 80 端口跳转之前就直接返回验证文件。

5. 测试站点加了 IP 白名单或密码保护

用 allow/deny 或 HTTP 基本认证保护的站点,会对 CA 返回 401 或 403。

  • 修复: 对验证路径豁免,非公开站点干脆用 DNS-01。

各环境修法

所有环境只需对 /.well-known/acme-challenge/ 放行,其他防护规则保持不变。

宝塔面板: 宝塔申请证书时会自动处理验证目录,但如果开了“防跨站”之外的自定义规则、宝塔防火墙的 CC 防御或“禁止海外访问”,验证请求可能被拦。到防火墙日志里确认,对 /.well-known/acme-challenge/ 加白名单,或在申请方式里改选 DNS 验证。

阿里云 / 腾讯云 WAF、CDN: 在 WAF 里给验证路径加精准访问控制的放行规则;CDN 要把这个路径回源到服务器且不缓存。

Certbot --nginx / --apache: 插件会临时加规则,但全站的 deny 规则或更早处理的 include 仍可能生效;反复失败就改用 --webroot,配合上面的 location。

WordPress 等网站程序: 一些安全插件会在 .htaccess 里禁止点开头的路径,需要把 /.well-known/acme-challenge/ 加进白名单。

Docker: 响应 80 端口的容器要挂载 Certbot 写入的同一个 webroot 卷。

不要这样做

以下做法要么削弱网站防护,要么耗光验证次数。

  • 删掉所有隐藏文件保护规则。 它们能防止 .git、.env 等文件泄露,只加一个精确的例外就行。
  • 对正式环境反复重试。 验证失败计入每小时 5 次的限制,见 Let's Encrypt 速率限制。
  • 把 WAF 整个关掉。 只豁免验证路径,或者改用 DNS-01。

常见问题

Let's Encrypt 会跟随跳转到 HTTPS 吗?

会。HTTP-01 从 80 端口开始,但会跟随到 80 或 443 端口上的 HTTP/HTTPS 跳转。如果 HTTPS 那边对验证路径返回 403,也会出这个错,所以两个 server 块里都要放行。

怎么测试才不浪费重试次数?

往验证目录写一个测试文件,从外部访问它。然后用 certbot certonly --dry-run,它走的是测试环境,不计入正式环境的限制。

返回 404 而不是 403 是什么意思?

404 表示服务器允许了请求但找不到文件:传给 Certbot 的 webroot 不是这个域名实际使用的网站目录,或者是另一个 server 块在响应。403 则是请求被拒绝。

国内服务器、域名没备案,会是这个错吗?

有可能。国内云服务器上未备案的域名,80/443 的访问会被云厂商拦截,返回的是拦截提示页而不是你的验证文件,状态码因厂商而异,Let's Encrypt 会报 unauthorized。这种情况要先完成备案,或者改用 DNS-01 验证。

资料来源

  1. Let's Encrypt: Challenge Types
  2. Certbot User Guide (webroot)
  3. RFC 8555 section 8.3: HTTP Challenge