基于Docker、Nginx、AWS ECS的Let's Encrypt验证频繁失败
我遇到过类似的ECS+Let's Encrypt验证问题,结合你的情况给你拆解分析下:
一、测试与定位问题的具体步骤
先一步步排查,缩小问题范围:
- 验证ELB流量分发:多次执行
curl -v http://your-domain/.well-known/acme-challenges/test.txt,看返回的响应是否来自不同的服务器IP(可以从Server头或IP地址判断)。同时查看ELB的访问日志,确认请求是不是随机分发到了所有ECS实例。如果是轮询分发,那只有一台实例有验证文件的话,大部分请求会404。 - 检查容器间的文件同步:CI/CD部署后,登录所有ECS实例,分别
docker exec到每个Nginx容器里,查看$SSL_ROOT/.well-known/acme-challenges目录下是否存在验证文件。如果只有一台容器有,那问题就出在文件没有同步。 - 核对Nginx配置:确保你的Nginx配置里对验证路径的规则没有被其他规则覆盖,比如:
这条规则一定要放在其他location ^~ /.well-known/acme-challenge/ { root $SSL_ROOT; default_type text/plain; allow all; # 禁用任何可能的缓存或rewrite expires -1; rewrite ^(.*)$ $1 break; }location规则之前,避免被比如Rails的try_files或HTTPS跳转规则拦截。 - 抓包跟踪验证请求:在ECS实例上执行
sudo tcpdump -i any port 80 and host acme-v02.api.letsencrypt.org,然后触发dehydrated验证,看请求是否真的到达了容器,以及容器返回的状态码是什么。 - 检查CI/CD脚本的执行范围:确认你的CI/CD是只在单台ECS实例上运行了dehydrated,还是所有实例都执行了。如果是前者,那只有那台实例的容器有验证文件。
二、为何仅单台服务器能成功验证?
核心原因就是验证文件没有同步到所有ECS容器,加上ELB的随机分发策略:
- 你的CI/CD流程大概率只在某一台ECS实例上执行了dehydrated脚本,生成的验证文件只存在于该实例的Nginx容器中。
- ELB默认用轮询分发请求,Let's Encrypt会从多个全球节点发起验证请求,这些请求会被ELB分到不同的ECS实例。只有当所有请求都打到有验证文件的那台实例时,验证才会成功——这种概率极低,所以你才会看到偶尔1/20次的成功。
- 日志里的部分200就是请求打到了那台正确的实例,而其他请求返回404/403,最终Let's Encrypt判定验证失败。
三、可落地的重构解决方案
给你几个不同复杂度的方案,按需选择:
方案1:用EFS共享验证文件(最通用)
在AWS ECS中挂载EFS到所有Nginx容器的$SSL_ROOT目录:
- 创建一个EFS文件系统,配置ECS任务定义,把EFS挂载到容器的
$SSL_ROOT路径。 - 修改dehydrated脚本,直接在EFS目录下生成验证文件——这样所有容器都能访问到同一个目录里的文件,不管ELB分发到哪台,都能返回正确的验证内容。
方案2:路由验证请求到特定实例(轻量改造)
调整ELB的路由规则,把验证请求单独转发到一台指定的ECS实例:
- 在ELB中添加一条路径匹配规则:
/.well-known/acme-challenges/*,转发到一个标记了acme-validator标签的ECS服务/任务。 - CI/CD脚本只在这个标记的任务上执行dehydrated,确保所有验证请求都打到有文件的实例。
方案3:改用DNS-01验证(最省心,适合Cloudflare用户)
既然你用Cloudflare管理DNS,直接切换到DNS-01验证方式:
- 修改dehydrated的配置,启用Cloudflare DNS插件,利用Cloudflare的API自动添加TXT记录完成验证。
- 这种方式不需要依赖HTTP流量,完全绕开了ELB和Nginx的分发问题,多实例场景下最可靠。
方案4:CI/CD同步执行到所有容器(无需额外AWS资源)
调整你的CI/CD流程,让dehydrated脚本在每一个运行中的Nginx容器里都执行一遍:
- 用AWS CLI的
aws ecs list-tasks获取所有运行中的任务ID,然后遍历每个任务,执行aws ecs execute-command来运行dehydrated脚本,确保每个容器都生成验证文件。
四、Cloudflare是否会导致该问题?
大概率会有影响,要检查这几个点:
- 缓存规则:Cloudflare默认可能缓存静态文件,包括
/.well-known/acme-challenges/下的内容。需要在Cloudflare的页面规则里添加一条:/.well-known/acme-challenges/*,设置Cache Level: Bypass,禁用缓存。 - WAF拦截:Cloudflare的WAF可能会把Let's Encrypt的验证请求当成恶意请求拦截,导致返回403。去Cloudflare的防火墙日志里查有没有来自Let's Encrypt IP段的请求被拦截,有的话添加允许规则。
- SSL与跳转设置:如果你的域名用了Cloudflare的橙色代理,要确保SSL模式是Full或Full (strict),并且没有强制把HTTP请求跳转到HTTPS——因为http-01验证必须用80端口的HTTP请求,强制跳转的话会导致验证失败。
内容的提问来源于stack exchange,提问作者iangolden
相关产品推荐
相关产品推荐

