ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux Nginx 代理超时排查时怎么抓包分析 TCP 挥手过程

Linux Nginx 代理超时排查时怎么抓包分析 TCP 挥手过程 前言Nginx 反向代理超时的直接后果是 504错误日志里常见这几种描述upstream timed out (110: Connection timed out) while connecting to upstream upstream timed out (110: Connection timed out) while reading response header from upstream upstream timed out (110: Connection timed out) while sending request to upstream upstream prematurely closed connection while reading response header from upstream no live upstreams while connecting to upstream光看日志你只知道超时了但不知道是后端没回、网络中间设备把它掐了、还是 Nginx 自己等不下去先撤了。抓包的价值就在这里TCP 的挥手FIN/RST方向和发生时刻是判断谁先放弃最硬的证据。本文基于 RHEL 9 / Ubuntu 22.04 nginx 1.24讲清三件事三类超时分别对应哪个阶段的包tcpdump里 FIN、RST、半关闭half-close怎么读以及一次真实的 504 抓包记录怎么逐行解释。文中示例均可在 Nginx 所在主机上直接执行。一、先分清超时发生在哪个阶段Nginx 的proxy模块有三个独立超时对应请求生命周期的三段抓包时看到的形态完全不同指令默认值覆盖阶段超时后日志里的动词proxy_connect_timeout60s与后端三次握手while connecting to upstreamproxy_send_timeout60s把请求体发给后端while sending request to upstreamproxy_read_timeout60s等待后端响应while reading response header from upstream三者默认都是 60 秒所以如果你没改过配置看到的行为就是请求发出后正好 60 秒出现 504。这个整整 60 秒本身就是线索第一节能省掉你不少瞎猜。另外还有两个只影响重试的指令proxy_next_upstream_timeout默认 0不限制和proxy_next_upstream_tries默认 0不限制次数。它们不产生自己的错误日志但会决定一次超时之后你还看不看得到后续记录。确认配置值时可以直接打印合并后的完整配置nginx -T 2/dev/null | grep -nE proxy_(connect|send|read)_timeout注意nginx -T需要 nginx 1.9.2 及以上版本更老的版本只能用nginx -t校验语法配置值从/etc/nginx/下的文件里翻。二、抓包前的准备抓包要在 Nginx 这台机器上做因为它同时看得到两个方向客户端到 Nginx、Nginx 到后端。抓的时候只保留后端相关的包避免把正常业务流量一起灌进来。# 1. 确认网卡名RHEL 9 常见 ens192Ubuntu 常见 ens33 ip -br a # 2. 装 tcpdump # RHEL/Rocky/AlmaLinux: sudo dnf install -y tcpdump # Debian/Ubuntu: sudo apt update sudo apt install -y tcpdump # 3. 抓包只抓与后端 10.0.0.11:8080 的往来落盘保存 sudo tcpdump -i any -nn -s0 -w /tmp/upstream.pcap host 10.0.0.11 and tcp port 8080几个参数的含义值得记住-i any抓所有网卡Linux 下会使用 cooked 抓包链路层信息与物理网卡不同但不影响 TCP 层分析-nn不做端口和地址的反向解析输出更快更干净-s0抓完整包避免只抓前 96 字节导致看不到 HTTP 内容-w写入 pcap 文件供后面反复分析。如果只想在终端上实时看把-w换成-nn -tttt -v其中-tttt输出带日期的绝对时间戳方便和 Nginx 的 error log 对时间。三、读懂 FIN、RST 与半关闭tcpdump用方括号里的字母表示 TCP 标志位对照表如下输出形式含义说明Flags [S]SYN发起连接Flags [S.]SYNACK接受连接Flags [.]ACK纯确认无数据Flags [P.]PSHACK携带数据Flags [F.]FINACK本方要关闭发送方向Flags [R]/Flags [R.]RST强制复位通常是异常正常关闭是四次挥手F.→.→F.→.如果只看包序列就是两方各发一个带 FIN 的包各自被对方确认一次。谁先发F.谁就是主动关闭方active closer谁就会在自己这一侧留下 TIME_WAIT。RST 则完全不同它直接终止连接不进入 TIME_WAIT数据可能已经丢了。抓包里看到 RST优先怀疑这几种情况后端进程崩溃或被 OOM killer 杀掉内核替它发 RST。连接在对端已经关闭之后还往上写数据对端回 RST。后端 listen 的 backlog 满了内核丢弃或拒绝新连接。中间有防火墙、四层负载均衡按空闲时间回收了连接客户端不知道继续用这条僵尸连接。还有一种是半关闭一方发了 FIN另一方还在继续发送数据F.之后紧跟P.包。这在流式响应SSE、大文件下载里很常见属于正常现象但如果 Nginx 侧先发了 FIN 而响应还在传输就要回头检查proxy_read_timeout是不是设得太紧。特别提醒Nginx 主动向上游发 FIN 不一定是故障。上游 keepalive 连接池里的空闲连接被回收超过keepalive_timeout或超过keepalive_requests时Nginx 就会发 FIN这是在正常清理连接。看到 FIN 要先看它前一条包是什么。实战一次 504 的抓包记录假设proxy_read_timeout 60s;后端接口/api/slow偶尔超时。按下面的顺序操作# 终端 A抓包 sudo tcpdump -i any -nn -s0 -w /tmp/upstream.pcap host 10.0.0.11 and tcp port 8080 # 终端 B复现 curl -o /dev/null -s -w code%{http_code} total%{time_total}\n http://127.0.0.1/api/slow # 抓够了以后 Ctrl-C 停掉终端 A然后离线分析 sudo tcpdump -nn -tttt -r /tmp/upstream.pcap | head -40只看带 FIN 或 RST 的包可以一步筛出来这个过滤表达式是 pcap-filter 语法tcp[tcpflags]取的是 TCP 标志位字节sudo tcpdump -nn -tttt -r /tmp/upstream.pcap tcp[tcpflags] (tcp-fin|tcp-rst) ! 0一次典型的后端处理慢记录如下时间戳做了简化两侧是源和目的14:22:31.104512 IP 10.0.0.10.51234 10.0.0.11.8080: Flags [S], seq 1001, win 29200, options [mss 1460,sackOK,TS val 33 ecr 0,nop,wscale 7], length 0 14:22:31.104633 IP 10.0.0.11.8080 10.0.0.10.51234: Flags [S.], seq 5001, ack 1002, win 28960, options [mss 1460,sackOK,TS val 88 ecr 33,nop,wscale 7], length 0 14:22:31.104701 IP 10.0.0.10.51234 10.0.0.11.8080: Flags [.], ack 1, win 229, length 0 14:22:31.104900 IP 10.0.0.10.51234 10.0.0.11.8080: Flags [P.], seq 1:120, ack 1, win 229, length 119: HTTP: GET /api/slow HTTP/1.1 14:22:31.105200 IP 10.0.0.11.8080 10.0.0.10.51234: Flags [.], ack 120, win 501, length 0 14:23:31.104900 IP 10.0.0.10.51234 10.0.0.11.8080: Flags [F.], seq 120, ack 1, win 229, length 0 14:23:31.105100 IP 10.0.0.11.8080 10.0.0.10.51234: Flags [.], ack 121, win 501, length 0 14:23:31.105400 IP 10.0.0.11.8080 10.0.0.10.51234: Flags [F.], seq 1, ack 121, win 501, length 0 14:23:31.105500 IP 10.0.0.10.51234 10.0.0.11.8080: Flags [.], ack 2, win 229, length 0逐行解读握手正常[S]、[S.]、[.]三行在 1 毫秒内完成proxy_connect_timeout没问题。TCP 时间戳选项TS val/ecr存在说明两端都开了tcp_timestamps。请求GET /api/slow已经发出并被后端确认说明proxy_send_timeout也没问题。从 14:22:31 到 14:23:31 整整 60 秒没有任何数据包。这个 60 秒正好等于proxy_read_timeout的默认值。14:23:31 这一毫秒里Nginx 侧10.0.0.10.51234先发[F.]然后后端确认并且回自己的[F.]最后 Nginx 确认四次挥手完整。全程没有 RST没有重传如果有重传会出现形如Flags [P.], seq 1:120的重复行或tcpdump输出的 retransmission 字样需要抓包时加-v才显示。结论很清楚不是网络问题是后端自己 60 秒没吐出一个字节Nginx 等到超时后主动关闭连接并返回 504。修复方向在后端慢查询、下游依赖超时、线程池排队而不是调proxy_read_timeout——把超时从 60 秒调到 120 秒只是把 504 延后用户等待时间更长。作为对照如果是中间设备掐连接你会看到 Nginx 在等待期间收到一个Flags [R]而且这个 RST 往往出现在一个不规整的时间点比如 30 秒、300 秒这种设备侧的空闲回收周期紧随其后 Nginx 才会记日志。这时候要看链路上有没有四层负载均衡、云厂商的安全组或者 NAT 网关它们的空闲超时必须大于 Nginx 的proxy_read_timeout。交叉验证用ss和日志变量缩小范围抓包之外两个更轻量的手段可以快速缩小范围。第一个是看 socket 状态和定时器ss需要 iproute2-o显示定时器-i显示 RTT、拥塞窗口、重传信息# 看与后端的连接、重传计数和 socket 定时器 ss -tion state established ( dport :8080 ) # 看一眼重传相关的全局统计 nstat -az | grep -iE TcpRetrans|TcpExtTCPLostRetransmit如果ss -ti里的retrans计数持续增长而抓包也确实看到同一个 seq 反复出现那就是链路丢包不是后端慢如果retrans一直是 0、RTT 稳定就是后端应用没回。第二个手段是给 access log 加上游时间变量把是不是超时直接变成可统计的指标log_format upstream_timing $remote_addr $request $status urt$upstream_response_time uct$upstream_connect_time uht$upstream_header_time rt$request_time;$upstream_connect_time大问题在握手阶段proxy_connect_timeout。$upstream_header_time大而$upstream_response_time更大说明后端迟迟不返回响应头。$request_time远大于$upstream_response_time问题在客户端到 Nginx 这一段与后端无关。这三个变量会随重试记录多次值之间用逗号分隔$upstream_addr同理会按顺序记下所有被尝试过的后端地址从这里能一眼看出重试打到了哪几台机器。常见坑点❌ 在业务服务器上抓包看到的是客户端方向的流量看不到 Nginx 与后端之间的挥手。 ✅ 抓包点选在 Nginx 主机上过滤条件锁定后端 IP 和端口。❌ 抓包不带-s0只抓到每个包前 96 字节HTTP 请求行和响应头被截断。 ✅ 用-s0抓完整包只要头部分析也可以用-A直接打印 ASCII 内容。❌ 只抓一个方向例如只抓src host 10.0.0.10挥手过程少了一半无法判断谁先发 FIN。 ✅ 过滤条件用host X and tcp port Y两个方向都要。❌ 看到 Nginx 发 FIN 就断定是故障忽略它前面是不是空闲了 60 秒。 ✅ 先看 FIN 之前的时间间隔和数据包内容空闲后清理是 keepalive 池的正常行为。❌ 把 RST 一律当成后端崩溃直接去重启后端。 ✅ RST 也可能是中间设备回收连接、或对端已关闭后继续写入导致要结合时间点和设备侧空闲超时判断。❌ 超时时间不敢动直接把proxy_read_timeout从 60s 加到 600s 当作修复。 ✅ 先抓包定性后端慢就治后端如果是链路上游设备掐连接则让上游设备的空闲超时大于 Nginx 侧超时。❌ 抓包文件不设上限长时间抓在生产机上把磁盘写满。 ✅ 加-c 数量限制包数或用-w配合-C按大小轮转抓完立刻删除 pcap 文件。总结抓包看到的形态指向的问题下一步长时间静默后 Nginx 先发 FIN时长等于某个超时值Nginx 侧超时触发确认对应阶段超时值修上游或调超时等待中出现 RST时间点不规律中间设备/防火墙回收连接调整链路设备空闲超时大于 Nginx 超时同一个 seq 反复出现ss -ti的 retrans 增长网络丢包查链路质量、MTU、网卡错误计数后端发 FIN 后紧跟响应数据半关闭流式响应正常无需处理确认业务是否符合预期三次握手阶段就超时只有 SYN 重传后端不可达或 backlog 满查后端监听状态、somaxconn抓包分析 TCP 挥手的关键不是把每个包都读懂而是回答一个问题谁先放弃为什么是那个时刻。把日志里的超时值、抓包里 FIN 出现的时间点、以及两端配置的空闲超时三者对齐归属就能立刻确定也不用再靠重启试试来碰运气。
返回列表