服务器故障排查:网站504错误的nginx错误日志分析和php-fpm参数调优
504 多半不是 nginx 的错,而是 php-fpm 排队超时。按清单查 nginx 错误日志定位是 upstream timed out 还是连接被拒,再调 php-fpm 进程数与超时参数,比反复重启有效。
清单 1:先确认这个 504 是谁返回的
先分清是 nginx 自己吐出来的,还是上游超时后转发过来的。浏览器 F12 看 Network 面板的响应头,curl -I https://example.com/xxx 看状态码和 Server 字段。再回到日志对时间点:如果同一秒有 upstream timed out (110: Connection timed out) while reading response header from upstream,方向是后端 PHP 没在超时窗口内返回;如果只有 connect() failed (111: Connection refused),方向是 php-fpm 没起来、socket 路径写错或权限不对。静态资源也 504,基本与 php-fpm 无关,去查 CDN 回源和机房链路。
清单 2:把 nginx 错误日志读出层次
日志默认在 /var/log/nginx/error.log,面板环境常见于 /www/wwwlogs/。别整篇翻,用命令抽特征:
grep -c "upstream timed out" /var/log/nginx/error.log
grep "upstream timed out" error.log | awk -F: '{print $1":"$2}' | sort | uniq -c | sort -rn | head
第二条按小时聚合,能看出峰值落在哪个时段。峰值和业务高峰重合,说明并发能力不足;全天零散出现,多半是个别慢脚本或外部接口卡住。同时留意的关键字还有 worker_connections are not enough(连接数上限)、Too many open files(句柄数)、no live upstreams(后端全被判死)。这几类各有各的处理路径,混在一起调参只会越调越乱。
清单 3:判断 php-fpm 进程池是否被打满
- 数进程:
ps aux | grep php-fpm | grep -v grep | wc -l,和配置里的pm.max_children对照,贴顶就是打满了。 - 开状态页:在池配置里加
pm.status_path = /fpm-status,nginx 加一个只允许内网访问的 location 转发到 fpm socket,然后curl http://127.0.0.1/fpm-status?full,重点看max children reached、listen queue、active processes。 listen queue长期大于 0,说明请求在排队;active processes长期等于max_children,说明并发容量见底。- 算内存:
free -m配合top -o %MEM,看单个 fpm 进程实际常驻多少。常见量级在三四十兆到七八十兆之间,差异取决于加载的扩展和框架。用可用内存除以单进程占用,再留出两成给系统、数据库和缓存,得到的才是能落地的max_children。
清单 4:php-fpm 参数调整顺序
- 选
pm模式。流量波动大用dynamic,稳定高并发用static,少一层进程调度开销。 - 定
pm.max_children。这是核心值,按清单 3 的内存反推结果填,不要照抄网上的固定数字。 - 配
pm.start_servers、pm.min_spare_servers、pm.max_spare_servers。dynamic 模式下按max_children的四分之一到二分之一取值,避免空闲进程空占内存。 - 设
pm.max_requests。给到几百到一千,缓解个别扩展的内存泄漏。 - 设
request_terminate_timeout。略大于 nginx 侧的读取超时,避免 nginx 早已返回 504、fpm 还在后台空跑。 - 改完必须验证:
php-fpm -t检查语法,再systemctl reload php-fpm平滑重载,不要 restart。池配置文件一般在/etc/php/{版本}/fpm/pool.d/www.conf,面板环境在/www/server/php/{版本}/etc/php-fpm.d/。动手前先cp www.conf www.conf.bak。
调大 max_children 不是万能钥匙。进程数上去,内存就被吃掉,MySQL 和 Redis 反而先 OOM,故障形态会从 504 变成 502 或整机失联。
清单 5:慢脚本与超时链条的联动
先开慢日志:request_slowlog_timeout = 5s、slowlog = /var/log/php-fpm/www-slow.log,跑上一两个小时,看慢在哪个文件哪一行。高频原因是循环里查库、同步调用短信或支付接口、没走索引的查询。
超时链条要按从小到大的顺序排:php.ini 的 max_execution_time < request_terminate_timeout < nginx 的 fastcgi_read_timeout 或 proxy_read_timeout。哪一层数值最小,报错就从哪一层冒出来。数据库侧顺带打开慢查询日志,long_query_time 设 1 秒起步,把耗时 SQL 先捞出来。上游若是另一台应用服务器,还要看 proxy_connect_timeout 是否过短,跨机房调用时它经常是隐形瓶颈。
清单 6:改完怎么验证、怎么回滚
- 压测:用
ab -n 2000 -c 100 https://example.com/或 wrk 打一轮,看失败率和 P95 耗时,别只看平均值。 - 观察:改完 24 小时再统计一次
upstream timed out条数,和改前对比量级,而不是凭感觉说"好像快了"。 - 看主机指标:阿里云控制台 → 云监控 → 主机监控,确认 CPU、内存、连接数没有出现新瓶颈;负载高但 CPU 不高,通常是 IO 或数据库在拖。
- 回滚:保留旧配置副本,
cp www.conf.bak www.conf && systemctl reload php-fpm,一分钟内恢复原状。 - 单机调参顶不住时,方向是加 php-fpm 节点、用 nginx upstream 做负载均衡,或把动态请求与静态资源拆到不同实例。改架构之前,先把日志和状态页的证据留全。