Nginx代理下Let's Encrypt证书自动续期失败问题排查
看起来你遇到的问题很典型——Nginx代理下ACME挑战路径能正常访问,但证书续期就是失败,直接让Node服务跑80/443端口就一切正常。我之前帮同事排查过几乎一模一样的情况,核心问题通常出在Nginx的配置细节或者greenlock对代理环境的适配上,下面是几个你可以逐一排查的方向:
1. 确保Nginx不重定向ACME挑战请求到HTTPS
Let's Encrypt的HTTP-01挑战要求必须通过HTTP 80端口直接获取验证文件,完全不允许3xx重定向。如果你的Nginx 80端口配置里有全局的HTTPS重定向规则,一定要把.well-known/acme-challenge的处理逻辑放在重定向规则之前,避免挑战请求被强制转到HTTPS。
正确的Nginx 80端口配置示例:
server { listen 80; server_name mydomain.com; # 优先处理ACME挑战,避免被重定向规则拦截 location /.well-known/acme-challenge/ { proxy_pass http://localhost:3000; # 关键:传递原始请求的Host头,greenlock需要验证域名匹配 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 其余普通请求再重定向到HTTPS location / { return 301 https://$host$request_uri; } }
2. 让greenlock信任代理环境
greenlock-express默认可能不会识别来自Nginx的代理请求,你需要在配置中明确开启代理信任,让它能正确解析代理传递的请求头,或者直接指定服务域名。
比如在你的greenlock初始化代码中添加proxy: true或者指定servername:
const greenlock = require('greenlock-express').create({ // 其他已有配置... servername: 'mydomain.com', // 明确指定你的服务域名 proxy: true, // 信任代理发来的请求 debug: true // 开启调试日志,方便后续排查 });
3. 查看greenlock的详细日志定位错误
有时候表面上挑战路径能访问,但实际续期时的请求可能存在隐性问题(比如响应头不符合要求、请求超时等)。开启greenlock的调试日志能帮你精准找到失败原因:
启动Node服务时带上DEBUG环境变量:
DEBUG=greenlock* node your-app-entry-file.js
然后手动触发续期(或者等自动续期触发),查看日志里的具体错误信息——比如是“challenge response mismatch”还是“connection timeout”,根据日志再针对性调整配置。
4. 禁用TLS-ALPN挑战(可选)
如果你同时启用了TLS-ALPN-01挑战(greenlock默认可能会尝试),那Nginx占用443端口会导致这个挑战直接失败。这种情况下要么让Nginx转发TLS-ALPN流量到Node的3001端口(配置复杂度较高),要么强制greenlock只使用HTTP-01挑战:
在greenlock配置中添加:
challenges: { http: { port: 3000 }, // 只启用HTTP-01挑战 tlsalpn: false // 禁用TLS-ALPN-01挑战 }
按照上面的步骤排查,应该能解决续期失败的问题。我之前就是因为Nginx的重定向规则顺序搞反了,导致挑战请求被强制转到HTTPS,续期一直失败,调整顺序后就正常了。
内容的提问来源于stack exchange,提问作者Timo

