Nginx 502/504 排查实战:从 upstream 超时到 php-fpm 进程池

    发布时间:2026-09-12 04:31 更新时间:2026-09-12 04:31 阅读量:0

    站点突然返回 502 Bad Gateway 或者 504 Gateway Time-out,页面上只剩一行白底黑字,运营群已经在问网站是不是挂了。这两个码来自不同环节:502 意味着 Nginx 连不上后端,或者连上之后被后端拒绝、连接断开;504 意味着连接建立成功、请求也发出去了,但没能在超时时间内拿到后端响应。把请求卡在哪一段先判断清楚,后面的排查才不至于眉毛胡子一把抓。

    一、先用日志把范围缩到一段

    Nginx 的 error.log 会记录上游返回的失败原因,配合 access.log 里的 upstream_status、upstream_response_time,很容易判断是个别接口慢还是整站不可用。日志目录在不同发行版里可能是 /var/log/nginx 或 /usr/local/nginx/logs,以实际环境为准。

    tail -n 50 /var/log/nginx/error.log
    grep -E "upstream (timed out|prematurely closed|sent invalid)" /var/log/nginx/error.log | tail -20
    grep -E "upstream timed out|upstream prematurely closed" /var/log/nginx/error.log | wc -l

    关键字对号入座:出现 connect() failed 或 Connection refused,多半是后端进程没起来、监听端口写错,或者被本机防火墙拦下;出现 upstream timed out,说明后端处理时间超过了阈值;出现 upstream prematurely closed connection,通常是 PHP 子进程被中途终止,比如脚本致命错误、内存超限被 OOM Killer 杀掉,或者被 request_terminate_timeout 掐断。

    接着看 php-fpm 自己的错误日志和慢日志,路径在 pool 配置里由 error_log、slowlog 决定,不同安装方式差异较大,具体以官方文档和实际环境为准。慢日志能直接告诉你是哪个脚本在拖时间,这一步往往比反复调超时参数更有价值。

    二、upstream 超时与 php-fpm 进程池怎么配合

    Nginx 侧和超时相关的有三个参数:fastcgi_connect_timeout 管建连,fastcgi_send_timeout 管把请求体发出去,fastcgi_read_timeout 管等待后端返回。504 绝大多数是 read_timeout 先到。但要注意,把 read_timeout 一味调大只是把症状往后推,如果后端本身跑得慢,用户等待时间照样很长。

    upstream php_backend {
        server unix:/run/php-fpm/www.sock;
        keepalive 32;
    }
    server {
        listen 80;
        server_name www.example.com;
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass php_backend;
            fastcgi_connect_timeout 3s;
            fastcgi_send_timeout 30s;
            fastcgi_read_timeout 30s;
            fastcgi_buffers 16 16k;
            fastcgi_buffer_size 32k;
        }
    }

    真正决定并发能力的是 php-fpm 进程池里的 pm.max_children。请求数长期贴着这个上限,新来的请求只能排队,排队时间从几毫秒涨到几十秒,前端表现就是 504;如果子进程被系统杀掉,前端就是 502。估算方法很朴素:用可用内存除以单个 PHP 进程的平均常驻内存,留出系统和其他服务的余量,不要直接把内存全部吃满。

    ps -o rss,cmd -C php-fpm | awk 'NR>1{s+=$1;n++} END{printf "进程数:%d 平均RSS:%.1fMB 合计:%.1fMB\n", n, s/n/1024, s/1024}'
    free -m

    拿到平均值后,再回到 pool 配置里调整。下面这份片段是最常见的几项,参数值需要按自己的机器改,不要照抄:

    pm = dynamic
    pm.max_children = 32
    pm.start_servers = 8
    pm.min_spare_servers = 4
    pm.max_spare_servers = 12
    pm.max_requests = 500
    request_terminate_timeout = 60s
    request_slowlog_timeout = 5s
    slowlog = /var/log/php-fpm/www-slow.log

    这里有个容易被忽略的配合关系:PHP 脚本自己的 max_execution_time、php-fpm 的 request_terminate_timeout、Nginx 的 fastcgi_read_timeout 三层如果顺序错乱,就会出现 Nginx 还在等、php-fpm 已经把进程杀掉的情况,日志里留下一句 prematurely closed。一般的思路是让脚本自身的执行上限最短,进程池的终止时间略长,Nginx 的等待时间再放一点余量,具体数值按业务接口的实际耗时分布来定。

    三、缓冲区、连接方式和几个常见坑

    还有一类 502 跟超时无关,是响应头太大导致的。上游返回的响应头超过 fastcgi_buffer_size 时,Nginx 会在 error.log 写下 upstream sent too big header while reading response header from upstream,客户端看到 502。典型触发场景是 PHP 写入体积不小的 Cookie,或者 URL 带着很长的参数、把会话信息塞进了头部。

    fastcgi_buffer_size 32k;
    fastcgi_buffers 8 32k;
    fastcgi_busy_buffers_size 64k;
    client_max_body_size 32m;

    顺带几个经常踩的点。Unix socket 比 127.0.0.1:9000 少一层网络开销,但要注意 socket 文件权限和 backlog,SELinux 开启时还可能直接拒绝 Nginx 访问该文件;磁盘写满会让 PHP 会话文件和日志写不进去,同样表现为 502,先 df -h 看一眼最省事;worker_connections、keepalive 设置过小,高并发下会表现为连接排队而不是立即报错。排查顺序建议是先看 error.log 归类,再直连后端绕开 Nginx 验证,判断问题出在哪一层。

    curl -o /dev/null -s -w "状态:%{http_code} 总耗时:%{time_total}s 建连:%{time_connect}s\n" https://www.example.com/
    curl -o /dev/null -s -w "状态:%{http_code} 总耗时:%{time_total}s\n" -H "Host: www.example.com" http://127.0.0.1/index.php

    如果开启了 php-fpm 的 pm.status_path,可以把它挂在一个只允许内网访问的 location 上,观察活跃进程、排队请求等指标,不过这个路径不要在公网上暴露,配置方式以官方文档为准。

    整套流程可以记成一句话:先分类,502 是连不上或被拒、504 是等不到,再分层,从 Nginx 到 socket 或 TCP,再到 php-fpm 进程池,最后落到 PHP 脚本本身,参数调整放在最后。改完配置别忘了 nginx -t 检查语法,然后用 systemctl reload nginx 和 systemctl reload php-fpm 平滑生效,并在低峰期观察一段时间,确认排队和错误数量是真的降下来了,再去考虑扩容或者把耗时逻辑挪到异步任务里。

    继续阅读

    📑 📅
    MySQL 出现 Too many connections 怎么办:定位、连接池与超时调优 2026-09-11
    Linux用户与权限管理实战:sudoers、SUID/SGID与最小权限 2026-09-10
    systemd服务管理实战:从Unit编写到开机自启与自动重启 2026-09-10
    网站服务器迁移完整流程:数据同步、解析切换与验证 2026-09-09
    服务器日志分析:grep、awk 与 GoAccess 快速排障指南 2026-09-09
    Docker 磁盘占用过高清理实战:overlay2、容器日志与悬空镜像 2026-09-12
    Let's Encrypt 证书自动续期失败排查:日志、80端口校验与 reload 钩子 2026-09-13
    Nginx 日志按天切割与过期清理:logrotate 实战 2026-09-15
    Docker Compose 部署多容器应用实战:环境变量、数据卷与重启策略配置要点 2026-09-15
    MySQL慢查询日志开启与优化入门 2026-09-15