页面转了半天,浏览器上出现「504 Gateway Time-out」,底下还跟着一行nginx版本号。同一时刻error.log里会多出这么一条:
2026/10/04 10:03:17 [error] 31#31: *482 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.10, server: example.com, request: "GET /report HTTP/1.1", upstream: "http://127.0.0.1:3000/report", host: "example.com"
504和502的差别就在这一段:502是后端给了坏响应或者压根连不上,504是后端活着,只是慢。先别急着改配置,得弄清慢的是哪一层。
报错串尾部的三个词,决定改哪个指令
上面那条报错的尾部有三种写法,每种对应不同指令,改错了不起任何作用。1、while connecting to upstream,对应proxy_connect_timeout,量的是建TCP连接的时间,后端几秒钟连不上通常是进程没了,调大这个值没意义。2、while sending request to upstream,对应proxy_send_timeout,量的是两次写请求之间的最大间隔,大文件上传到慢后端时才可能碰到。3、while reading response header from upstream,对应proxy_read_timeout,量的是两次读之间的最大间隔,平时碰到的504基本都是它。
三个值默认都是60s。连PHP-FPM时换成fastcgi_connect_timeout、fastcgi_send_timeout、fastcgi_read_timeout,默认值同样60s;uwsgi_*_timeout和grpc_*_timeout也一样。还有个容易搞反的点:proxy_read_timeout是空闲计时器,不是总耗时上限,后端每吐一个字节它就重置一次,所以流式响应挂在连接上不管多久都不超时,一声不吭卡死的查询必定超时。
改哪个看报错串,别三个一起调。
先确认504是不是nginx发的
504未必来自nginx。看响应头里的Server字段:Server是nginx才算;Server是awselb/2.0,那是ALB的idle timeout在掐,默认60s;带X-Cache: Error from cloudfront的是CloudFront,origin response timeout默认30s;页面是Cloudflare那种样式,看它报的数字,524表示源站接了连接但没在125s内吐出字节(早年的文档写100s,现在官方改成125s了),522是连接阶段超时。这些层在nginx外面,光调nginx一点用没有。
还有个笨办法:掐表数一下。504稳定在60s左右出现,指向nginx或ALB;30s左右出现,指向CloudFront或Gunicorn;125s左右出现,指向Cloudflare。数一下就能把范围缩到一层。
把耗时打进日志,别靠猜
error.log只能说明超时了,看不出是哪个URL慢。给access日志加两个变量:
log_format main '$remote_addr $request $status rt=$request_time urt=$upstream_response_time';
$request_time是从收到请求第一个字节到写完日志的总时间,$upstream_response_time是nginx等后端的时间,走过多个upstream时用逗号分隔。改完reload一下,出问题时把504的那几行捞出来:
grep " 504 " /var/log/nginx/access.log | tail -20
想单独复测某个后端,绕开nginx直接压一次:
curl -w '%{time_starttransfer}\n' -o /dev/null -s http://127.0.0.1:3000/report
这个数是首字节时间。实测过不少台机器,首字节1秒以内却照样504,问题多半在后面某条SQL或者外部接口上,不在nginx。
后端自己那把刀通常落下得更快
nginx等60s,PHP可等不了这么久。PHP 7.4、8.0、8.1、8.2、8.3、8.4,max_execution_time默认30s,脚本跑到30s被自己掐断,nginx收回一个断掉的连接,页面变成502或者空白,而不是504。现场见过改了max_execution_time却不生效的,原因是php-fpm池配置里用php_admin_value把它锁死了,脚本里的ini_set()会被直接忽略。fpm池里的request_terminate_timeout默认是0,也就是不限制,一旦设了值它比max_execution_time还硬,到点直接kill掉worker,连register_shutdown_function都跑不完。
其它后端同样:Gunicorn默认timeout是30s,uWSGI的harakiri默认关闭,Tomcat的connectionTimeout是20s。这些值比nginx小,外层调再大也白搭。数据库那层也要看一眼,MySQL的innodb_lock_wait_timeout默认50s,一条SQL卡在锁上50s,还等不到nginx的60s就会先抛出1205或者1213;把long_query_time设成1s开慢日志,抓出来的SQL补上索引,504常常就自己消失了。
超时之后nginx还会再试一台
这点最容易被忽略。proxy_next_upstream的默认值是error timeout,超时也算失败,nginx会拿这个请求再去找下一台upstream,两台都卡的话用户等的时间直接翻倍,60s变成120s,而日志里只留一条504。
非幂等请求另说。默认nginx不会把POST这类请求转给下一台,配置里一旦写了non_idempotent,下单接口也会被重试,后端收到两笔,前台只看到一次504。看到配置里有这个参数,先确认它是不是必要的。
间歇性504要找规律
不是每个请求都504,而是偶尔504、刷新一下又好了,这种最磨人。按时间排一排:每天固定时段出现的,多半是定时扫表任务;只有某几个URL出现的,是那几段代码里有慢查询;参数特别多时才出现的,是筛选条件没走索引。
之前接过一台机器,504只在大促前半小时冒出来,翻fpm-status发现active processes长期顶在上限,进程全在排队,后面的请求还没轮到就超时了。加进程数只能缓解,把那几个慢接口改成队列任务才解决问题。
长任务别占着请求
导出报表、批量同步这类活,天然就要跑几分钟。硬把超时调到600也能跑完,用户却盯着白屏等,中间网络一抖前功尽弃。请求进来立刻返回任务id,后台慢慢跑,前端轮询或者做完了通知,nginx那层维持默认60s就行。
真要临时放宽,别在server块里全局改,只对那一个location单独设:
location /report { proxy_pass http://backend; proxy_read_timeout 300s; }
nginx -t && nginx -s reload
改完务必先跑nginx -t,语法错了reload会失败,站点会继续跑旧配置。nginx 1.18.0、1.24.0、1.28.0,这几个版本里指令写法一致,Ubuntu 22.04带的是1.18.0、24.04带的是1.24.0、25.10带的是1.28.0。
499别混进来看
access日志里还有个499,是客户端自己先断开,nginx记的码,跟504无关。用户等不及点了停止,或者前端axios设了10s的timeout先超时,都会留下499。看到一堆499混在504里,先去前端把timeout调大,否则nginx这边改了大半天,用户那边早就撤了。
数据截至2026年10月,上面这些默认值在nginx 1.18.0到1.28.0之间没有变过。