
文章目录
504 是 Nginx 等上游超时,不是 PHP 一定崩了。先看 Nginx error_log 里 upstream timed out,再查 PHP-FPM slowlog 与 MySQL 慢查询;把 request_terminate_timeout、pm.max_children、外部请求超时配好并压测复现,才算做对。
504 / 502 / 499 的区别
做 Nginx 504 排查,第一步是分清状态码。三者都出现在 Nginx 日志,但成因完全不同,混为一谈会直接走错方向。
| 状态码 | 含义 | 典型日志 | 根因方向 |
|---|---|---|---|
| 504 | Gateway Timeout,Nginx 等上游 PHP-FPM 超时未返回 | upstream timed out (110: Connection timed out) while reading response header from upstream | PHP 执行太久、FPM 忙、外部 API 卡住 |
| 502 | Bad Gateway,Nginx 连不上或收到无效响应 | connect() to unix:/run/php/php8.3-fpm.sock failed (111: Connection refused) | FPM 没启动、socket 权限错、进程崩 |
| 499 | Client Closed Request,客户端先断开 | 无 upstream 错误,access_log 状态码 499 | 前端超时、用户关页、CDN 回源超时 |
快速定位命令:
# 2026 年常见路径(Debian/Ubuntu 上 PHP 8.3/8.4)
# 看 504 的 upstream 报错
grep 'upstream timed out' /var/log/nginx/error.log | tail -20
# 看 access_log 里 504/499 分布
awk '$9 ~ /^(504|499)$/ {print $9}' /var/log/nginx/access.log | sort | uniq -c
# 确认 FPM 在不在
systemctl status php8.3-fpm
ls -l /run/php/php8.3-fpm.sock
踩坑:很多人一看 504 就去调大 fastcgi_read_timeout,结果只是把报错推迟。判断做对的标准是——改完后用压测复现,504 消失且没有变成 502 或 499。
慢查询与外部请求超时
504 最常见的根因不是 Nginx,而是 WordPress 在等 MySQL 或外部 HTTP。2026 年 WordPress 6.7+ 仍大量依赖 WP_Query、WP_Http,外部请求默认没有短超时。
MySQL 慢查询
先看有没有慢查询。MySQL 8.0/8.4 上:
# 临时开慢日志(重启失效,适合线上止血排查)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
# 实时抓
tail -f /var/log/mysql/slow.log
# 用 mysqldumpslow 汇总
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
WordPress 侧常见杀手:未分页的 WP_Query、缺失索引的 postmeta 查询、Autoload 巨大的 wp_options。检查 autoload:
SELECT option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size DESC LIMIT 10;
如果单行超过 800KB,考虑清理或改为 autoload = ‘no’。这和对象缓存策略直接相关,可参考 WordPress 对象缓存:Redis 到底能解决什么问题 来把重复查询挡在数据库前。
外部 HTTP 请求卡死
WordPress 的 wp_remote_get() 默认 timeout 是 5 秒,但很多插件传了更长的值,或在循环中调用。2026 年仍建议显式设短超时:
获取报价
邮箱 + 需求 + 预算区间,24 小时内回复报价。
$response = wp_remote_get( 'https://api.example.com/data', array(
'timeout' => 3,
'redirection' => 2,
'blocking' => true,
) );
if ( is_wp_error( $response ) ) {
error_log( 'external api fail: ' . $response->get_error_message() );
return;
}
$body = wp_remote_retrieve_body( $response );
同时看 PHP 侧是否卡在 curl:
# 看当前 FPM 进程在干什么(需装 strace)
strace -p $(pgrep -f 'php-fpm: pool www' | head -1) -s 200 -tt
# 或者用 gdb 打栈(php8.3 带 debug symbol 时)
gdb -p $(pgrep -f 'php-fpm: pool www' | head -1) -ex 'bt' -batch
验证做对:外部接口挂掉时,页面应在 3 秒后降级或报错,而不是 60 秒后 Nginx 抛 504。回退方法:把 timeout 改回原值前先备份 functions.php 或插件文件。
FPM 进程池与队列
即使 SQL 和外部请求都快,FPM 池子满了也会排到超时。2026 年 PHP-FPM 配置仍在 /etc/php/8.3/fpm/pool.d/www.conf(或 8.4)。
关键参数
- pm.max_children:同时最多几个 worker。太小会排队。
- pm.max_requests:处理多少请求后重启,防内存泄漏。
- request_terminate_timeout:单个请求硬杀时间,必须小于 Nginx 的 fastcgi_read_timeout。
- listen.backlog:socket 队列长度,满了就丢连接。
# /etc/php/8.3/fpm/pool.d/www.conf 片段
pm = dynamic
pm.max_children = 40
pm.start_servers = 8
pm.min_spare_servers = 4
pm.max_spare_servers = 12
pm.max_requests = 500
request_terminate_timeout = 30s
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm.slow.log
listen = /run/php/php8.3-fpm.sock
listen.backlog = 511
Nginx 侧对应(/etc/nginx/conf.d/site.conf 或 sites-enabled):
location ~ .php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_read_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_connect_timeout 10s;
}
注意:request_terminate_timeout(30s)必须 < fastcgi_read_timeout(60s),否则 PHP 先被杀、Nginx 还在等,日志会变成 502 而非干净的 504。
怎么看队列堵了
# FPM 状态页需先开 pm.status_path = /fpm-status
curl http://127.0.0.1/fpm-status
# 关注:
# listen queue: 队列长度
# active processes: 活跃进程
# max children reached: 是否触顶
# 系统层看 socket 队列
ss -lx | grep php8.3-fpm
如果 listen queue 持续大于 0 且 max children reached 大于 0,就是池子不够或请求太慢。先加 max_children(受内存限制:每个 FPM 进程约 30-60MB),再回头查慢请求。
实施步骤:从日志到止血与根治的完整流程
- 用
grep 'upstream timed out' /var/log/nginx/error.log确认是 504 而非 502/499,记录时间点。 - 同时段查 PHP-FPM slowlog(
/var/log/php8.3-fpm.slow.log),拿到卡住的文件与函数名。 - 开 MySQL slow log 抓 2 秒以上查询,检查 wp_options autoload 与缺失索引。
- 检查所有 wp_remote_get/wp_remote_post 的 timeout 参数,统一压到 3-5 秒。
- 调 FPM:request_terminate_timeout 小于 fastcgi_read_timeout,按需加 pm.max_children。
- 用
ab或wrk压测复现,确认 504 消失且无 502/499 转移。
压测示例:
# 模拟 20 并发、200 请求
ab -n 200 -c 20 https://example.com/
# 或 wrk
wrk -t4 -c50 -d30s https://example.com/
失败回退:改 www.conf 前先 cp www.conf www.conf.bak;改完 php-fpm8.3 -t 校验语法,再 systemctl reload php8.3-fpm。若 reload 后 502,立刻还原备份并 reload。
临时止血与根治

临时止血
线上已经 504 时,先恢复服务:
# 1. 重启 FPM 清掉僵死 worker
systemctl restart php8.3-fpm
# 2. 临时放宽 Nginx 超时(争取排查时间,不是解药)
sed -i 's/fastcgi_read_timeout .*/fastcgi_read_timeout 120s;/' /etc/nginx/conf.d/site.conf
nginx -t && systemctl reload nginx
# 3. 停掉明显作恶的插件(进库或文件改名)
UPDATE wp_options SET option_value='' WHERE option_name='active_plugins';
第 3 步会关掉所有插件,仅用于争取时间;之后要逐个恢复。更安全的做法是移走单个插件目录:mv wp-content/plugins/heavy-plugin wp-content/plugins/.heavy-plugin.off。
根治
止血后必须做根因修复:
- 给慢查询加索引:
ALTER TABLE wp_postmeta ADD INDEX meta_key (meta_key(191)); - 外部请求全部走短超时 + 失败降级,必要时用 Transients API 缓存结果。
- FPM 常驻参数固化进配置管理(Ansible/Puppet),不靠手工改。
- 引入 APM 或日志告警:504 出现 5 次/分钟就通知。
如果站点本身流量模型复杂、需要长期有人值守,可以交给专业团队维护。我们提供的 WordPress 企业网站开发 服务包含部署架构评审与故障兜底,适合把这类问题前置到上线前解决。
另一个常被忽略的根治动作是备份与回滚能力:修改 FPM/Nginx 前确保能还原。可参考 WordPress 备份策略:3-2-1 原则与恢复演练 建立演练习惯。
常见问题
调大 fastcgi_read_timeout 就能解决 504 吗?
不能。那只是让 Nginx 等更久,请求仍卡在 PHP 或 MySQL。必须配合 slowlog 找到慢点,否则超时只是被推迟。
request_terminate_timeout 设成 0 是不是更好?
不是。0 表示不限制,坏请求会永久占住 worker,池子迅速耗尽。应设为小于 fastcgi_read_timeout 的值,如 30s。
为什么重启 FPM 后暂时好了又复发?
说明有持续慢请求或泄漏。重启只清了当前进程,根因(慢 SQL、外部 API、插件循环)还在,需按流程继续查。
可执行的动作清单
- 今天就在 error.log 里跑一遍
grep 'upstream timed out',确认 504 规模。 - 打开 PHP-FPM slowlog 并设 request_slowlog_timeout = 5s。
- 检查 wp_options autoload 体积,超过 800KB 的行做清理。
- 把所有 wp_remote_* 的 timeout 显式设为 3-5 秒。
- 校准 request_terminate_timeout 与 fastcgi_read_timeout 的层级关系并 reload。
想让 WordPress 在高并发下不再卡死?看我们的 WordPress 技术服务,或从 技术栏目 继续深入。
