Nginx请求超时排查:从504到高并发调优的完整指南
凌晨两点监控群连弹三条告警一套刚上线的接口服务开始大面积出现504。打开Nginx错误日志刷屏的全是同一句话upstream timed out (110: Connection timed out) while reading response header from upstream。那会儿我的第一反应是“后端挂了”可登上去一查Tomcat进程活得好好的CPU也不高。折腾到天亮才想明白Nginx 请求超时这件事从来都不是调一个参数就能解释清楚的。它背后牵涉TCP连接、上游应用状态、DNS解析、TLS握手、连接池复用甚至文件下载时的流式转发。这篇文章不是来背参数手册的。我会把日常排障里真正遇到的超时场景拆开讲超时发生在哪一层、nginx.conf里哪些配置项对应哪段链路、常见的真实根因长什么样、完整的排查过程怎么走以及高并发下几个容易被忽略的坑。无论你是刚入门配Nginx的新手还是被线上超时问题折腾得睡不着的运维按这条思路走一遍基本能把问题定位到具体环节。1. 超时报错不是一种病先分清你碰到的是哪一类的“超时”1.1 从响应码和日志阶段词判断超时类型很多人一看到“超时”两个字就直接去翻proxy_read_timeout这是最常见的误区。Nginx里的超时至少分三类对应不同的响应码和日志特征。第一类是504 Bad Gateway。这是Nginx作为反向代理等待上游服务器返回响应头或响应体时超时。它在error.log里的典型形态是upstream timed out (110: Connection timed out) while reading response header from upstream注意冒号里的110是系统errnoConnection timed out说明是socket级别的超时。日志里还会写明超时发生在哪个阶段while connecting to upstream、while sending request to upstream、while reading response header from upstream。这三个阶段词非常关键它们直接告诉你Nginx卡在了哪一步是TCP连不上、请求发不出去还是连上了但对方一直不给响应。第二类是408 Request Timeout。这通常是客户端侧的问题比如客户端请求头半天没发完、上传body时长时间停顿。Nginx默认会在client_header_timeout和client_body_timeout到期后返回408。虽然日志里可能不会出现刺眼的“upstream timed out”但用户体感就是“请求超时了”。第三类是502 Bad Gateway它和超时有交叉但又不完全一样。很多时候502来自“连接被拒绝”或“连接后立即被重置”比如上游端口没监听、防火墙丢包、后端进程主动断开。它也可能以“connect() failed (111: Connection refused)”出现这种不是超时而是连接失败。我在实际排障中习惯把502和504放在一起看因为它们都指向“Nginx连不上/等不到上游”但处置路径不同。这里有个容易被忽略的细节同一段日志里超时类型决定了你要看哪个配置项而不是笼统地把所有timeout参数都调大。后面第2节我会把每个参数对应的链路拆开讲。1.2 request_time和upstream_response_time两个变量帮你定位“卡在谁家”除了看error.logaccess.log里的两个内置变量比很多人想象的更有用$request_time和$upstream_response_time。$request_time是Nginx处理这次请求的总耗时从收到客户端第一个字节开始算到响应发送完毕为止。$upstream_response_time则是Nginx从开始连接上游到收到完整响应头或响应体的时间它是数组类型如果配置了多个上游每次请求都可能记录多个值。强烈建议在生产环境日志格式里加上这两个字段排查超时问题时它们能省掉大量猜测。我常用的log_format片段是这样的log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent req_time$request_time upstream_time$upstream_response_time upstream_status$upstream_status host$host;怎么用这两个值判断方向如果request_time和upstream_response_time都很高且数值接近说明卡点在上游Nginx只是背锅的。如果request_time很高但upstream_response_time很低说明上游响应很快时间都花在了“等待连接上游”或“给客户端回包”上。前者要看连接池和proxy_connect_timeout后者要看带宽、客户端下载速度和send_timeout。如果upstream_response_time是-说明压根没连上上游或者上游连接失败这时候要重点看proxy_connect_timeout和网络连通性。有一次我排查一个接口偶发超时从access.log里发现大量请求upstream_time0.002但req_time30s上游明明秒回请求却卡了30秒才结束。最后定位到是Nginx那一侧的worker连接数被打满新请求在等空闲worker而阻塞在accept队列上。这种问题如果只盯着上游调proxy_read_timeout永远调不好。2. 配置层拆解nginx.conf里每个timeout到底管哪一段2.1 客户端侧这四个参数决定的是“入口”不阻塞client_header_timeout控制读取客户端请求头的超时时间默认60秒。这里说的超时不是“整个请求头必须在60秒内传完”而是“两次读操作之间的间隔不能超过60秒”。所以慢速网络环境下请求头慢慢传是不会触发超时的只有客户端中途停住不再发送Nginx才会掐断连接。client_body_timeout同理控制读取请求body时的超时。对大文件上传场景特别敏感。我见过一个案例用户上传几GB的备份文件因为网络抖动上传过程中出现超过设定阈值的停顿Nginx直接断开连接客户端看到的却是“请求超时”。这时候调大client_body_timeout有效但更合理的做法是让上传走专门的服务或对象存储不要扛在Nginx的body转发上。send_timeout容易被人忽略它管的是向客户端发送响应的超时。同样是两次写操作之间的间隔而不是总耗时。适合大文件下载、慢速客户端、流式接口场景。如果客户端下载速度太慢或中途暂停下载send_timeout到期后Nginx会断开连接表现为下载中断或“连接超时”。keepalive_timeout则控制客户端与Nginx之间空闲长连接保持的时间默认75秒。它本身不会导致“请求超时”但如果设得太短客户端频繁重新建连TCP握手开销会叠加在请求延迟里高并发时表现成整体变慢。2.2 反向代理最常见的三个参数connect、read、send代理到上游时Nginx有三个独立参数分别对应连接、读响应、发请求三个阶段proxy_connect_timeout默认60秒指Nginx与上游建立TCP连接的超时。proxy_read_timeout默认60秒指Nginx从上游读取响应的超时。proxy_send_timeout默认60秒指Nginx向上游发送请求的超时。这三个参数里proxy_read_timeout是日常排障中出现频率最高的。它管的是“从上游读取响应头/响应体的两读间隔超时”和业务处理时长强相关。如果后端接口业务逻辑本身就要跑80秒而proxy_read_timeout是默认的60秒那客户端必然收到504而且日志里一定是while reading response header from upstream。proxy_connect_timeout的问题场景不太一样。它默认60秒但如果是跨机房、跨云调用网络延迟和丢包率偏高时TCP握手可能要好几秒遇到抖动就会触发connect超时。这里有个经验connect超时基本都说明“网络不通/不稳定”调大参数只能缓解表象真正要做的是检查防火墙、路由、对端是否监听。Nginx不仅代理HTTPFastCGI场景PHP也有对应的fastcgi_connect_timeout、fastcgi_read_timeout、fastcgi_send_timeout原理和代理参数一致。用uWSGI或gRPC时同理只是前缀不同。2.3 还有一个“编译期”的timeout容易被忽略用yum或apt装Nginx配置文件里的默认值会被封装好但如果你是自己编译安装的Nginx源码里其实也有一部分“写死”的默认值比如某些模块内部对超时时间的宏定义。大部分场景用不到真正要留意的反而是编译时选择的依赖库版本。举个例子如果你在aarch64纯内网环境离线编译Nginx用的PCRE版本过旧、OpenSSL版本有已知问题TLS握手性能和正则匹配效率会明显下降。原本几毫秒的握手可能变成几百毫秒高并发下会累积成“大量请求超时”。而且内网环境经常没有配好NTP证书校验时的时间偏差会引发TLS协商失败用户看到的也是“超时”。所以我的建议是编译安装Nginx时从国内镜像源下载源码和依赖优先选与当前Nginx版本兼容性好的PCRE版本OpenSSL用长期支持版本。源码包本身下载慢其实是整个部署流程里最容易“超时”的第一步。2.4 一张速查表遇到问题先看对应参数现象优先检查参数默认值说明504日志提到while reading response headerproxy_read_timeout/fastcgi_read_timeout60s上游处理太慢或上游长时间不吐数据504/502日志提到while connecting to upstreamproxy_connect_timeout60s网络不通、上游未监听、防火墙拦截504日志提到while sending request to upstreamproxy_send_timeout60s上游接收能力异常较少见408请求头没传完client_header_timeout60s慢速客户端或恶意连接408body传了一半停了client_body_timeout60s上传场景、大文件、网络抖动下载中断、流式接口断连send_timeout60s客户端读得太慢频繁新建连接导致整体变慢keepalive_timeout75s设置太短导致握手开销增加注意上表告诉你看哪个参数不代表直接把所有timeout调到300秒就是对的。参数是给业务兜底的不是用来掩盖后端问题的。正确的做法是先让参数合理再让后端在规定时间内返回。3. 真正让请求超时的根因通常藏在nginx后面3.1 后端“假活真死”TCP还连得上业务已经卡死这是我最常碰到的一类“假故障”。现象是Nginx日志里大量upstream timed out while reading response header但你去连后端端口TCP是通的进程也活着CPU和内存看起来都正常。于是后端说“我没问题”运维说“nginx在报超时”两边互相拉扯。这种局面的本质是TCP连接建立成功但业务线程已经全部阻塞或排队没人来读请求、写响应。Java场景里常见的是线程池打满、GC停顿、数据库连接池耗尽PHP-FPM场景则是max_children耗尽新请求全部在队列里等Node.js单线程被CPU密集型任务卡住也一样。怎么验证“假活真死”在Nginx所在机器上直接对上游发起一次请求看响应时间curl -s -o /dev/null -w dns:%{time_namelookup}s connect:%{time_connect}s appconnect:%{time_appconnect}s starttransfer:%{time_starttransfer}s total:%{time_total}s\n http://10.0.3.11:8080/api/health如果connect毫秒级完成而starttransfer或total要几十秒甚至直接卡到超时那就不要再怀疑Nginx了。问题在上游应用内部排查线程堆栈、数据库连接池、慢查询才是正路。3.2 连接池耗尽、线程排队高并发下的连锁超时高并发时的超时往往是“排队”引起的而不是“处理慢”。比如一个Tomcat应用默认maxThreads200当同时有300个请求进来前200个在处理后100个在Tomcat的连接器队列里排队。这100个请求在Nginx侧看到的等待时间越来越长一旦超过proxy_read_timeout就变成504。这种场景有个明显的特征Nginx日志里upstream_response_time梯度上升开始是1秒、5秒最后直接变成60秒附近正好卡在超时阈值上。而后端机器CPU可能还没打满因为线程都在等锁或者等外部依赖。处置上调大proxy_read_timeout是治标真正要做的是给后端扩容、优化慢接口、调线程池参数或者把同步阻塞改成异步处理。3.3 慢SQL、慢依赖带来的“半死不活”延迟后端业务本身没有死锁但某个接口内部串行调用了多个外部依赖每个依赖慢个两三秒加起来就超过Nginx超时阈值了。最典型的是慢SQL数据库CPU高某条查询跑了20秒前端接口一直等Nginx等到proxy_read_timeout到期就断连。我排查过一个“每天下午4点准时出现一批504”的案例最后发现是定时任务在4点做全表统计把数据库IO打满所有关联查询变慢。这类问题调Nginx参数没有任何意义得从业务SQL、缓存、索引下手。Nginx侧的合理做法是让超时阈值和业务SLA匹配如果接口SLA是“99%的请求在30秒内返回”那proxy_read_timeout设成60秒是合适的如果接口本身就允许跑3分钟那就别用默认60秒硬扛。3.4 DNS解析和TLS握手这两个“隐藏时间”不少超时不在业务处理上而是发生在连接建立阶段只是表象被“请求超时”统一掩盖了。一种情况是upstream配置里用的是域名Nginx在每次新建上游连接时都可能触发DNS解析。如果你没有显式配置resolver解析结果还会被缓存但一旦DNS服务器响应慢或域名解析失败后重试proxy_connect_timeout就开始计时。表现出来就是偶发性超时一会儿好一会儿坏。另一种情况是TLS握手。客户端到Nginx之间如果跑HTTPS握手阶段要协商加密套件、交换证书。证书链不完整是重灾区很多人在证书服务商那里下载证书后只配了服务器证书和私钥没配中间证书链。结果是部分客户端握手失败或非常慢用户看到的就是“加载超时”。双向TLS场景里如果客户端没带客户端证书Nginx会直接断开浏览器报错形式也接近“连接超时/请求失败”。另外内网机器时间不同步会导致证书有效期校验失败这种问题连日志都很难查表现成“莫名其妙的超时”。3.5 文件下载和autoindex在线浏览的特殊场景如果你用Nginx做文件共享、在线目录浏览常见的autoindex功能超时的特征又不一样。大文件下载时Nginx默认开sendfile从磁盘到网卡是内核态零拷贝本身很快。但如果你的架构是“Nginx反代到后端文件服务”下载流要经过Nginx转发那么proxy_read_timeout控制的是“上游两次发送数据之间的间隔”。源站在大文件传输中偶发暂停比如磁盘读取卡顿超过阈值就断流。autoindex在线文件浏览是另一个坑开启autoindex on之后目录索引页本身是Nginx生成的对磁盘IO有依赖。当目录文件数达到几万个生成索引页可能会很慢。更麻烦的是公网环境里别人一旦发现你的目录没有限速就开着多线程下载带宽被打满其它小请求全部排队看起来就像“所有接口都超时”。如果你确实要用autoindex做共享文件建议配合limit_rate限制单连接速度并用limit_conn限制并发连接数防止一台下载机拖垮整个Nginx。4. 一次完整排查实录从“一片超时”到定位根因4.1 先确认Nginx本身活着配置能正常加载有一次客户报“网站全部超时”我登上去一看Nginx进程根本不在。再查配置发现有人在Windows上改配置时引用了不存在的路径启动直接报错nginx: [emerg] createfile() d:/phpstudy_pro/www/admin2.com/nginx.htaccess failed配置都加载不了进程自然起不来用户侧看到的就是“一直转圈最后超时”。所以排查任何Nginx超时问题第一步永远是ps -ef | grep nginx nginx -t确认master进程和worker进程都在配置语法没问题。如果进程掉过、配置被改过先把服务恢复起来再谈参数调优。另一个基础检查是多实例站点配置。Debian系发行版默认会include /etc/nginx/sites-enabled/*如果你在主配置里改了proxy_read_timeout但某个站点的server块里又写了一层覆盖配置主配置里的值可能根本不会生效。我自己踩过这个坑改了conf.d下的文件以为生效了reload也成功了结果请求仍然60秒超时最后发现sites-enabled下的配置优先级更高。4.2 打日志锁定超时发生在哪个环节确认Nginx正常后我会先把access.log格式里加上request_time、upstream_response_time、upstream_status如果之前没加再临时把error.log级别调到notice以便看到更完整的连接信息。接着复现超时抓一批报错样本。假设我看到这样的记录req_time60.003 upstream_time60.001 upstream_status504两个时间几乎相等且都卡在60秒基本可以断定响应时间全部消耗在“和上游打交道”上。再配合error.log里while reading response header from upstream方向就很明确——上游处理或返回慢了Nginx的proxy_read_timeout到期。如果看到req_time60.002 upstream_time-那就要看是connect失败还是send失败。error.log里会写清楚是while connecting to upstream还是while sending request to upstream。4.3 在Nginx机器上直接验证上游拿到方向后我习惯在Nginx所在机器上绕过Nginx直接测上游接口。注意不要用浏览器测也不要在开发机测一定要在Nginx那台机器上才能模拟最接近真实链路的网络路径。用curl的详细时间输出curl -s -o /dev/null -w connect:%{time_connect}s starttransfer:%{time_starttransfer}s total:%{time_total}s http_code:%{http_code}\n http://127.0.0.1:8080/api/order/list如果本机直连上游都过了60秒没返回那基本实锤是上游业务慢。如果本机直连上游很快但经过Nginx后就超时那要检查Nginx侧的转发配置、连接池复用、以及Nginx到客户端之间的链路。有个细节值得提starttransfer和total之差是“响应头到了之后下载body的耗时”。如果body很大且客户端下载慢send_timeout才会介入这时候upstream_time反而不高。我见过有人分不清“上游慢”和“响应传输慢”把proxy_read_timeout调大了一倍问题依旧因为该调的是send_timeout。4.4 临时放大阈值验证再按根因修复当怀疑是阈值太紧导致504时可以先把proxy_read_timeout临时放大到120秒reload后观察是否还报超时。这里的逻辑是如果还没到120秒就不再报错说明后端最终能返回只是超过了原阈值如果到了120秒依然超时说明后端彻底卡死调参没用必须从后端入手。有一次排查一个定时任务接口每次跑到90秒左右就断我把proxy_read_timeout临时调到180秒请求就能成功返回。对比后端执行日志确认该接口确实需要100秒左右的处理时间。最后做法是把这个接口拆分到另一个内网调用链路上单独设置更长的超时避免拖累整个站点的超时约束而不是简单粗暴地把全局超时调到300秒。还有一种情况临时调大后请求成功了但每个请求都占用Nginx和上游连接很长时间并发一高就又把连接数打满。所以“调大超时验证”只能作为诊断手段不能作为长期修复方案。5. 高并发场景下的超时调优几个容易被忽略的坑5.1 worker和连接数上限其实是超时问题的“地基”超时不是只看那几个timeout参数。如果你的Nginx worker进程数太少或者最大连接数上限太小请求会阻塞在“等待worker处理”这个环节上表现依然是大面积超时但error.log里可能连“upstream timed out”都没有而是迟迟不发响应。我常用的基础配置长这样worker_processes auto; worker_rlimit_nofile 65535; events { use epoll; worker_connections 65535; } http { keepalive_timeout 65; keepalive_requests 1000; server_tokens off; }worker_processes auto会按CPU核数自动生成worker进程worker_connections是单worker能同时保持的最大连接数乘以worker数量就是总连接上限worker_rlimit_nofile要同步抬高否则连接数上限会被“文件描述符不够”卡住表现为连接建立失败或者非常缓慢用户侧接近“超时”。这些参数不是越大越好。每开一个连接都有内存开销worker数超过CPU核数反而增加上下文切换开销。调优本质是让连接数、worker数、后端处理能力三者匹配。5.2 upstream keepalive减少握手是治本思路很多超时不是“超时参数不够大”而是“连接建立太频繁”。Nginx反代到上游时默认情况下每个请求都会新建TCP连接请求结束后关闭。高并发时频繁的TCP三次握手和四次挥手会消耗大量时间也让上游的TIME_WAIT连接暴涨。解决办法是开启upstream连接复用upstream backend { server 10.0.3.11:8080; server 10.0.3.12:8080; keepalive 32; } server { location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ; } }关键是两条proxy_http_version 1.1默认反代用的是HTTP/1.0不支持keepalive以及proxy_set_header Connection ;清掉请求头里的Connection字段避免上游误以为要关闭连接。keepalive 32表示Nginx每个worker进程会保留最多32个到上游的空闲连接。开启之后效果非常明显减少了握手耗时上游的TIME_WAIT数量下降整体请求延迟稳定很多。在高并发压测时这个配置经常能把超时率从百分之几降到零。5.3 用对fail_timeout和proxy_next_upstream别让半死节点拖垮全局Nginx负载均衡里有个容易被忽略的机制max_fails和fail_timeout。它们控制的是“上游节点在fail_timeout时间内失败max_fails次后会被标记为不可用并在fail_timeout期间不再转发流量”。举个例子upstream backend { server 10.0.3.11:8080 max_fails2 fail_timeout10s; server 10.0.3.12:8080 max_fails2 fail_timeout10s; }如果10.0.3.11在10秒内连续2次连接失败Nginx会在接下来的10秒内把它摘掉流量全部打到健康节点。这个配置能有效避免“一个半死节点拖垮所有请求”请求排到它头上就超时。proxy_next_upstream则是另一种策略。当某个上游返回错误如connect失败、read超时、HTTP 502/503/504时Nginx可以把请求转发给下一个上游。但这招有风险如果请求已经写入了数据库再转发给另一个节点可能造成重复写入。非幂等请求建议不要开。5.4 踩坑清单这些年我遇到的超时相关“假故障”把容易让新手抓狂的坑集中列一下每一条都是我实际踩过或帮别人擦过屁股的坑现象解决思路主配置改了timeout但子配置覆盖明明调了proxy_read_timeout请求仍然60秒超时grep -r timeout /etc/nginx/全盘检查注意sites-enabled和conf.d的优先级reload后配置没生效改了配置reload成功行为没变化平滑升级/重启Nginx注意reload不一定重新加载所有模块的运行时状态NTP未同步导致TLS证书校验失败请求偶发握手超时日志没有明确报错检查系统时间配置内网时间同步源autoindex on的目录被当下载站带宽被打满所有接口都慢加limit_rate和limit_conn或把共享文件迁移到独立存储上游域名DNS解析慢每个新连接的connect阶段耗时偏高在upstream里用IP或配置resolver开启keepalive复用连接后端接口本身要跑120秒无论怎么调Nginx都偶发504拆分长任务或单独为长任务接口设置更长超时而不是全局调整旧版本Nginx有已知连接异常漏洞偶发连接重置、响应异常建议升级到修复后的稳定版本安全升级本身也是排障手段之一这里要特别说下“调大所有timeout”的诱惑。我见过有人把proxy_read_timeout、proxy_connect_timeout、send_timeout全改成600秒表面上问题消失了但后端真的卡死时Nginx会毫无感知地挂着600秒连接数被僵尸请求占满雪崩只是晚一点到来。超时参数应该是“业务SLA的护栏”不是“后端问题的遮羞布”。最后再分享一个我个人的习惯给每个站点的server块单独设置业务相关的timeout不要全局统一。比如下载站点send_timeout可以给长一点接口主线保持60秒长任务接口单独location覆盖。这样既不会让某个慢接口拖累全局也不会让普通接口被慢业务绑架。超时排障这件事说到底就是要搞清楚“谁在等谁”把这个逻辑理顺了绝大多数问题都能快速落地。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →