发布时间: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 决定,不同安装方式差异较大,具体以官方文档和实际环境为准。慢日志能直接告诉你是哪个脚本在拖时间,这一步往往比反复调超时参数更有价值。
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 |