一次网站 504 故障里,最容易让人走偏的动作,是看到 Nginx 日志里的 upstream timed out,马上把超时时间从 60 秒改成 300 秒。页面可能暂时不报错了,但后端为什么需要一分钟仍然没有答案。
这条日志真正提供的是一段链路证据:Nginx 已经把请求交给上游,却没有在规定时间内读到下一段响应。
排查时需要先确认 timeout 的类型,再定位具体请求、上游服务和停顿阶段,具体怎么做一起往下看吧。

一、先把完整日志读完
典型错误可能是:
upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 203.0.113.10, server: example.com,
request: "POST /wp-admin/admin-ajax.php HTTP/2.0",
upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock",
host: "example.com"
这条示例日志已经给出四个关键证据:请求是 POST /wp-admin/admin-ajax.php,上游为 PHP-FPM Socket,超时发生在读取响应头阶段,访问者 IP 也被记录下来。
如果日志写的是 while connecting to upstream,应先查端口、Socket、进程和网络连接;如果是 while reading response header from upstream,说明连接大概率已经建立,后端迟迟没有返回响应头。两类报错不能用同一条加大超时命令处理。
二、给访问日志增加上游耗时
只看 error.log,能知道请求超时,却不知道超时前后是否还有大量慢请求。更有效的做法是临时给访问日志记录总耗时和上游耗时:
log_format upstream_timing '$remote_addr "$request" $status '
'rt=$request_time '
'uct=$upstream_connect_time '
'uht=$upstream_header_time '
'urt=$upstream_response_time '
'ua=$upstream_addr';
access_log /var/log/nginx/upstream_timing.log upstream_timing;
修改后先检查并平滑重载:
sudo nginx -t
sudo systemctl reload nginx
uct 很高时更像连接上游缓慢;uht 很高表示等待响应头耗时;urt 是完整上游响应时间。某一个接口持续变慢,和所有页面同时变慢,后续排查方向完全不同。
三、绕过Nginx直接测试上游
反向代理到 HTTP 服务时,可以在服务器本机直接请求上游:
curl -sS -o /dev/null \
-w 'connect=%{time_connect} starttransfer=%{time_starttransfer} total=%{time_total}\n' \
http://127.0.0.1:3000/health
直接访问也慢,问题在应用、数据库或它依赖的外部接口;直接访问稳定,而经过 Nginx 才慢,则继续检查代理配置、连接复用、请求体、限流与中间网络。
PHP-FPM 使用 Unix Socket 时,不能把上面的 HTTP 请求原样套用。应检查 PHP-FPM 状态、慢日志、进程池和应用日志。不同运行方式需要不同证据,不能为了让教程看起来统一而混用命令。
四、沿着请求时间线找瓶颈
把一次慢请求按时间顺序拆开,排查会更清楚:
| 证据 | 更可能的方向 | 下一步 |
|---|---|---|
| 上游连接时间高 | 服务未监听、连接队列或网络问题 | 查端口、Socket、连接数 |
| 响应头时间高 | 应用代码、数据库、外部API | 查慢日志与调用链 |
| 只在高峰出现 | 进程池、连接池、CPU或I/O饱和 | 对齐同一时刻监控 |
| 固定接口出现 | SQL、任务逻辑或第三方请求 | 单独复现该接口 |
| 重载后短暂恢复 | 资源泄漏、连接堆积 | 查进程与连接增长 |
这一步的关键不是收集更多截图,而是让 Nginx 日志、应用日志、数据库慢查询和系统监控使用同一时间范围。CPU 平均值正常并不能排除单核打满、磁盘等待或 PHP-FPM 工作进程全部占用。
五、为什么直接调大timeout可能有害
Nginx 官方说明中,proxy_read_timeout 和 fastcgi_read_timeout 控制的是两次连续读取操作之间允许等待的时间,不是整个响应必须在该时间内完成。默认值为 60 秒。
把它改成 300 秒,只会让 Nginx 更久地保留慢请求。后端进程池已经耗尽时,等待时间更长还可能让连接堆积得更严重。真正需要长时间运行的导出、生成或计算任务,更适合改为异步任务,前端轮询结果,而不是让一个 HTTP 请求一直占住资源。
超时调整只有两种情况比较合理:业务本身明确允许较长同步请求,或者已经定位并修复根因,只需要给合理的峰值留余量。修改时应只作用于必要的 location,不要无差别提高全站超时。
六、从PHP-FPM和数据库继续验证
PHP-FPM 场景先看服务和进程池:
sudo systemctl status php8.3-fpm --no-pager
sudo journalctl -u php8.3-fpm --since "-15 minutes"
ps -eo pid,etime,%cpu,%mem,cmd | grep '[p]hp-fpm'
如果日志出现 server reached pm.max_children setting,说明某个时段工作进程已经用完。此时既要评估 pm.max_children,也要找到进程为什么长时间不释放。盲目增加进程数会继续消耗内存,并把压力推向数据库。
数据库侧应把同一时间段的慢查询、锁等待和连接数对齐。一个缺少索引的后台查询、WordPress 插件的集中任务,或者超时的外部 API,都可能让 PHP 请求停住。只有证据落到具体调用上,优化才不会变成参数接力。
七、最小改动与验收标准
我更建议每次只改一个关键点:修复慢查询、限制集中任务、调整进程池,或仅为特定接口增加合理超时。修改前记录慢请求的 uht、urt 和错误次数,修改后用同一请求与相近负载复测。
问题真正解决至少应满足:
- 错误日志在一个完整业务高峰内不再出现同类 timeout。
- 目标接口的上游响应时间回到可接受范围,而不是刚好低于新的超时值。
- PHP-FPM、数据库连接或应用工作线程没有持续耗尽。
- 业务操作完整成功,不只是 Nginx 不再返回 504。
如果只是把 60 秒改成 300 秒,接口从 504 变成等待 240 秒后成功,这不算性能问题修复,只是把失败改成了更久的等待。
相关问题
问:日志里的110是Nginx版本号吗?
答:不是。这里的110通常对应系统层的连接超时错误。
问:proxy_read_timeout和fastcgi_read_timeout选哪个?
答:看上游类型。proxy_pass 使用前者,FastCGI/PHP-FPM 使用后者,应以实际配置和日志中的 upstream 为准。
问:修改后需要重启Nginx吗?
答:先运行 nginx -t,通过后执行平滑 reload 即可,不必直接停止服务。


