I
Editorial
Nginx 做反向代理,最常见的两个报错就是 502 Bad Gateway 和 504 Gateway Timeout。它们看起来相似,但含义不同,排查方向也不同。本帖整理一套从日志入手的排查顺序,适用于 Nginx 代理到应用服务(PHP-FPM、Node.js、Java、Python 等)的常见场景。
一、先分清 502 和 504
1. 502:Nginx 连上了上游,或者试图连接,但拿到的是无效响应——连接被拒绝、连接被重置、上游提前关闭连接、响应头格式不对等。
2. 504:Nginx 等上游等到超时了,上游没在规定时间内建立连接或返回响应。
简单记:502 多半是“上游挂了或断了”,504 多半是“上游太慢”。
二、第一步永远是看 error.log
错误日志里的关键字基本能直接指明方向:
1. connect() failed (111: Connection refused):上游端口没监听,服务没起来或者挂了,或者 upstream 地址/端口写错。
2. connect() to unix:/run/php/php-fpm.sock failed (13: Permission denied):Unix socket 权限问题,Nginx 的运行用户没有访问 socket 的权限。
3. upstream prematurely closed connection while reading response header:上游处理过程中把连接关了,常见于应用进程崩溃、被 OOM 杀掉,或者应用自身超时先断开。
4. recv() failed (104: Connection reset by peer):连接被对端重置,常见于上游 keepalive 超时比 Nginx 短,Nginx 复用了一个上游已经关闭的连接。
5. upstream timed out (110: Connection timed out) while reading response header:典型 504,上游响应太慢。
三、502 的排查顺序
1. 确认上游存活:在 Nginx 所在机器上直接 curl 上游地址(例如 curl -v http://127.0.0.1:8080/health),绕过 Nginx 看是否正常。
2. 看上游进程和系统日志:应用日志有没有异常堆栈;dmesg 或 journalctl 里有没有 OOM killer 的记录。
3. PHP-FPM 场景:看 FPM 的 error log 是否出现 server reached pm.max_children,进程池满了,新请求排不上;同时打开 slowlog 找出慢脚本。
4. keepalive 不匹配:如果开启了 upstream keepalive,确保上游服务的空闲连接超时大于 Nginx 侧的 keepalive_timeout,避免 Nginx 拿到一个已被上游关闭的连接。
5. 容器环境:上游容器重启后 IP 变了,而 Nginx 只在启动时解析一次域名,会一直连旧地址。可以配置 resolver 并用变量写 proxy_pass,让 Nginx 按 TTL 重新解析,或者在服务发现变化后 reload。
四、504 的排查顺序
1. 先确认慢在哪:在 access_log 里加上 $request_time 和 $upstream_response_time 两个变量。前者是整个请求耗时,后者是上游耗时,两者对比能看出时间花在上游还是网络/客户端。
2. 找慢请求的共同点:是某个接口、某类参数,还是某个时间段集中出现?常见根因是慢 SQL、调用外部接口没设超时、锁等待。
3. 再考虑超时参数:proxy_connect_timeout(建连)、proxy_read_timeout(两次读之间的间隔)、proxy_send_timeout(两次写之间的间隔),默认都是 60 秒。注意 proxy_read_timeout 不是整个响应的总时长,而是相邻两次读操作的间隔。
4. 不要一上来就把超时调到几分钟。超时调大只是让用户等得更久,还会让 Nginx 和上游积压更多连接。长任务更合适的做法是改成异步:提交任务立即返回任务号,前端轮询或用回调获取结果。
五、几个容易忽略的点
1. 多层代理:CDN → 负载均衡 → Nginx → 应用,每一层都有自己的超时,最外层超时如果比内层短,用户会先看到外层的 504,内层其实还在处理。各层超时应由外到内逐级略大。
2. 大文件上传:client_max_body_size 太小会直接返回 413,不是 502;但上传慢、上游读请求体超时,也可能表现为 504。
3. proxy_next_upstream:默认在连接错误和超时时会尝试下一台上游,对非幂等的 POST 请求要谨慎,避免重复提交。
六、建议的日志格式
在 log_format 中加入 $upstream_addr、$upstream_status、$upstream_response_time、$request_time,出问题时能直接看到是哪台上游、返回了什么、用了多久。这一步花五分钟,后面能省很多时间。
大家线上遇到过哪些比较隐蔽的 502/504?欢迎补充案例。
一、先分清 502 和 504
1. 502:Nginx 连上了上游,或者试图连接,但拿到的是无效响应——连接被拒绝、连接被重置、上游提前关闭连接、响应头格式不对等。
2. 504:Nginx 等上游等到超时了,上游没在规定时间内建立连接或返回响应。
简单记:502 多半是“上游挂了或断了”,504 多半是“上游太慢”。
二、第一步永远是看 error.log
错误日志里的关键字基本能直接指明方向:
1. connect() failed (111: Connection refused):上游端口没监听,服务没起来或者挂了,或者 upstream 地址/端口写错。
2. connect() to unix:/run/php/php-fpm.sock failed (13: Permission denied):Unix socket 权限问题,Nginx 的运行用户没有访问 socket 的权限。
3. upstream prematurely closed connection while reading response header:上游处理过程中把连接关了,常见于应用进程崩溃、被 OOM 杀掉,或者应用自身超时先断开。
4. recv() failed (104: Connection reset by peer):连接被对端重置,常见于上游 keepalive 超时比 Nginx 短,Nginx 复用了一个上游已经关闭的连接。
5. upstream timed out (110: Connection timed out) while reading response header:典型 504,上游响应太慢。
三、502 的排查顺序
1. 确认上游存活:在 Nginx 所在机器上直接 curl 上游地址(例如 curl -v http://127.0.0.1:8080/health),绕过 Nginx 看是否正常。
2. 看上游进程和系统日志:应用日志有没有异常堆栈;dmesg 或 journalctl 里有没有 OOM killer 的记录。
3. PHP-FPM 场景:看 FPM 的 error log 是否出现 server reached pm.max_children,进程池满了,新请求排不上;同时打开 slowlog 找出慢脚本。
4. keepalive 不匹配:如果开启了 upstream keepalive,确保上游服务的空闲连接超时大于 Nginx 侧的 keepalive_timeout,避免 Nginx 拿到一个已被上游关闭的连接。
5. 容器环境:上游容器重启后 IP 变了,而 Nginx 只在启动时解析一次域名,会一直连旧地址。可以配置 resolver 并用变量写 proxy_pass,让 Nginx 按 TTL 重新解析,或者在服务发现变化后 reload。
四、504 的排查顺序
1. 先确认慢在哪:在 access_log 里加上 $request_time 和 $upstream_response_time 两个变量。前者是整个请求耗时,后者是上游耗时,两者对比能看出时间花在上游还是网络/客户端。
2. 找慢请求的共同点:是某个接口、某类参数,还是某个时间段集中出现?常见根因是慢 SQL、调用外部接口没设超时、锁等待。
3. 再考虑超时参数:proxy_connect_timeout(建连)、proxy_read_timeout(两次读之间的间隔)、proxy_send_timeout(两次写之间的间隔),默认都是 60 秒。注意 proxy_read_timeout 不是整个响应的总时长,而是相邻两次读操作的间隔。
4. 不要一上来就把超时调到几分钟。超时调大只是让用户等得更久,还会让 Nginx 和上游积压更多连接。长任务更合适的做法是改成异步:提交任务立即返回任务号,前端轮询或用回调获取结果。
五、几个容易忽略的点
1. 多层代理:CDN → 负载均衡 → Nginx → 应用,每一层都有自己的超时,最外层超时如果比内层短,用户会先看到外层的 504,内层其实还在处理。各层超时应由外到内逐级略大。
2. 大文件上传:client_max_body_size 太小会直接返回 413,不是 502;但上传慢、上游读请求体超时,也可能表现为 504。
3. proxy_next_upstream:默认在连接错误和超时时会尝试下一台上游,对非幂等的 POST 请求要谨慎,避免重复提交。
六、建议的日志格式
在 log_format 中加入 $upstream_addr、$upstream_status、$upstream_response_time、$request_time,出问题时能直接看到是哪台上游、返回了什么、用了多久。这一步花五分钟,后面能省很多时间。
大家线上遇到过哪些比较隐蔽的 502/504?欢迎补充案例。