ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux Nginx proxy_send_timeout 参数怎么配置优化大文件上传

Linux Nginx proxy_send_timeout 参数怎么配置优化大文件上传 前言先纠正这个标题里的前提proxy_send_timeout并不是上传超时把它调大通常也解决不了大文件上传失败。官方文档对它的定义是Nginx 向被代理服务器上游发送请求时的超时且只在两次相邻写操作之间计时不是整个请求的传输时长。方向是 Nginx 到后端不是浏览器到 Nginx。更关键的是默认行为proxy_request_buffering的默认值是on意思是Nginx 会先把客户端的请求体完整收下来超过内存缓冲的部分落到临时文件收完之后才向后端发起请求、把 body 转发过去。所以在默认配置下浏览器上传大文件的过程根本没有触碰到proxy_send_timeout——真正决定成败的是客户端到 Nginx 这一段的那几个参数。这就解释了一类很常见的现象上传大文件失败把proxy_send_timeout从 60s 调到 600s一点用都没有而把client_max_body_size从默认的1m改大问题立刻消失。proxy_send_timeout真正开始起作用是在你把proxy_request_buffering off打开、让 Nginx 边收边转发之后——这时候 Nginx 向上游的写入节奏完全由客户端上传速度决定proxy_send_timeout才成为上传速度的实际约束。本文把这条完整链路拆开讲清楚哪一段归哪个指令管、怎么定位日志、以及两种工作模式下分别该怎么配。示例基于 nginx 1.24 / 1.22。一、先分清四个超时各自管哪一段指令默认值计量方式管的是client_header_timeout60s读请求头整体客户端发请求头太慢client_body_timeout60s两次相邻读操作之间客户端上传 body 时的停顿send_timeout60s两次相邻写操作之间Nginx 把响应写回客户端时的停顿proxy_connect_timeout60s建连Nginx 连不上上游proxy_send_timeout60s两次相邻写操作之间Nginx 把请求发给上游时的停顿proxy_read_timeout60s两次相邻读操作之间上游迟迟不返回keepalive_timeout75s空闲长连接空闲回收这张表里最重要的是计量方式这一列。这几个超时的语义都是相邻两次 I/O 之间的间隔而不是整个请求从开始到结束最长多久。所以一个逻辑上传了 30 分钟的大文件只要数据一直在流动每两次写之间没有超过阈值就不会触发超时反过来一个只传了 3 秒的连接如果中间卡了 61 秒没动静照样会被掐掉。二、大文件上传的完整链路每个阶段谁在管按数据流动的顺序走一遍把每个阶段的控制点标出来阶段控制指令默认值典型症状客户端发请求头client_header_timeout60s少见的头部读取超时请求体超过上限client_max_body_size1m413 Request Entity Too Large请求体在内存里还是落盘client_body_buffer_size8k\16k临时文件放哪client_body_temp_path默认在编译前缀下的client_body_temp目录满 → 500客户端上传时的停顿client_body_timeout60swhile reading client request bodyNginx 向上游发 bodyproxy_send_timeout60s只在proxy_request_buffering off时才是主要约束上游处理后返回proxy_read_timeout60swhile reading response header from upstreamNginx 把响应写回客户端send_timeout60s客户端下载响应太慢被断开第一件该做的事就是把client_max_body_size调到你需要的量级。默认值1m意味着任何超过 1MB 的上传都会在请求头解析完后立刻被拒客户端拿到 413连数据都没开始传# 允许 2GB 的请求体设为 0 表示不检查即不限制 client_max_body_size 2g;这条指令可以放在http、server、location里按站点或按路径精细控制比全局放开更合适。三、两种工作模式缓冲与流式模式 A默认的缓冲模式Nginx 先把整个请求体收完内存缓冲不够就落临时文件再转发给上游。它的好处是上游不必处理慢速上传、请求可以被重试和均衡代价是磁盘要能承下所有并发上传的体积之和且上游要等到全部收完才开始处理。location /upload/ { proxy_pass http://127.0.0.1:8080; client_max_body_size 2g; # 默认 1m必须调 client_body_timeout 300s; # 客户端慢速上传的停顿容忍 client_body_buffer_size 1m; # 超过此值写入临时文件 proxy_send_timeout 300s; # 收完之后发给上游时的停顿容忍 proxy_read_timeout 300s; # 上游处理大文件可能需要更久 # 临时目录不写则使用编译时的默认值。写的话目录必须存在且属主正确 client_body_temp_path /var/lib/nginx/body 1 2; }client_body_temp_path后面可以跟最多三层子目录参数如上面的1 2作用是把大量临时文件分散到多级子目录里避免单目录海量文件导致性能下降。这个目录必须已经存在并且属主是该 worker 进程运行的用户RHEL 系通常是nginxDebian 系是www-data。目录不存在时 Nginx 不会自动创建上传会以 500 失败。模式 B关闭请求缓冲边收边转发location /upload/ { proxy_pass http://127.0.0.1:8080; proxy_request_buffering off; # 1.7.11 起可用默认是 on proxy_http_version 1.1; # 需要流式/分块传输时要显式指定 client_max_body_size 2g; client_body_timeout 300s; proxy_send_timeout 300s; # 这个模式下它才真正管住上传速度 proxy_read_timeout 300s; }这个模式才有本文标题所说的意义Nginx 一边从客户端读一边往上游写客户端的上传速度直接决定了写入间隔。上传慢的客户端会让proxy_send_timeout变得敏感这时把它调大是合理的。但要注意三个真实代价上游必须能处理流式请求。Nginx 可能无法预先知道请求体长度会以分块传输chunked方式发给上游因此通常需要proxy_http_version 1.1并且上游得支持分块编码。请求不能再被重试或转投另一台上游。数据已经开始发出去中途失败就没法悄悄重发。用proxy_next_upstream的常规策略在这里帮不上忙。上游会提前看到慢客户端。应用可能因为读不到完整数据而超时这是把缓冲挪走之后的必然代价需要在应用侧相应调整。选择标准很直白希望降低 Nginx 的磁盘占用和首字节延迟用模式 B希望上游逻辑简单、能重试、磁盘足够用模式 A。四、定位从日志判断卡在哪一段上传失败时error.log里的一句话通常就能定位到具体指令。下面是几类常见记录的对照不同 Nginx 版本的措辞略有差异以你机器上的实际输出为准grep -E too large body|timed out|no space|prematurely /var/log/nginx/error.log | tail -30error.log 片段对应指令client intended to send too large body: 3145728 bytesclient_max_body_sizeclient timed out (110: Connection timed out) while reading client request bodyclient_body_timeoutupstream timed out (110: Connection timed out) while sending request to upstreamproxy_send_timeoutupstream timed out (110: Connection timed out) while reading response header from upstreamproxy_read_timeoutupstream prematurely closed connection while sending request to upstream上游自己断了可能是它自己的限制(28: No space left on device) while reading client request body临时目录所在分区写满顺手确认最终生效的值避免改了没生效nginx -T 2/dev/null | grep -n -E client_max_body_size|client_body_timeout|client_body_temp_path|proxy_(send|read)_timeout|proxy_request_buffering再看临时目录的余量以及配置块之外的限制也别忘了# 找到实际使用的临时目录并看剩余空间 df -h /var/lib/nginx # SELinux enforcing 时目录标签不对也会失败 sudo ls -Zd /var/lib/nginx/body sudo restorecon -Rv /var/lib/nginx/body五、动手复现和验证用一个大文件走一遍完整流程同时观察各阶段的耗时。# 1) 造一个 200MB 的测试文件GNU coreutils 的 dd 支持 statusprogress dd if/dev/zero of/tmp/blob.bin bs1M count200 statusprogress # 2) 用表单方式上传并把各阶段时间与上传速率打出来 curl -s -o /dev/null \ -w connect%{time_connect} starttransfer%{time_starttransfer} total%{time_total} speed_upload%{speed_upload}B/s http%{http_code}\n \ -F file/tmp/blob.bin https://upload.example.com/upload/%{speed_upload}是 curl 报的实际上传速率%{http_code}能直接告诉你是不是 413。把这两组数字在调整参数前后各测一次收益是你自己测出来的不需要参考任何人的经验值。想验证proxy_send_timeout到底怎么触发可以在测试环境里扮演一个只收不答、收得极慢的上游# OpenBSD 版 netcat 的写法 nc -l 8080 /dev/null # 部分传统版 netcat 需要写成 # nc -l -p 8080 /dev/null让proxy_pass指向这个端口再上传一个大文件就能在error.log里看到while sending request to upstream的字样——用它来确认你的参数确实作用在这一段上比读文档印象深得多。调试阶段还有一个很有用的指令它把请求体无条件写进文件client_body_in_file_only on; # 取值 on | clean | off默认 off仅用于排障用它确认请求体有没有完整到达 Nginx可以把客户端没传完和上游没收到两种情况区分开。用完记得改回off。六、调参的原则成组调按最慢的合法用户定这几个超时是成组生效的只调一个往往没意义。至少要同时考虑client_body_timeout、proxy_send_timeout、proxy_read_timeout、send_timeout。取值依据应该是最慢的合法用户用第五节的方法测出真实上传耗时再留 2 到 3 倍余量而不是随手写个3600s。超时不是限速。想限速用limit_rate对响应或limit_conn应用层限速。把client_body_timeout设小来限速只会让正常用户传大文件失败。调大超时等于允许连接占住 worker 更久连接数会上升记得同步检查worker_connections与worker_rlimit_nofile。临时目录所在分区的容量要按并发上传数 × 单文件大小估算。这条最容易被忽略而它一旦满了症状是 500 而不是超时容易排查到别的方向去。常见坑点❌ 上传大文件失败第一反应是把proxy_send_timeout调到 600s。 ✅ 默认proxy_request_buffering on时这个超时管的是 Nginx 收完 body 之后发往上游的过程。先看是不是 413也就是client_max_body_size默认仅1m。❌ 把proxy_send_timeout理解成整个请求最长可以传多久。 ✅ 它的语义是相邻两次写操作之间的最大间隔且只作用于 Nginx 到上游这一段。整个请求传得久没关系中间长时间没有数据流动才会被断开。❌ 只调client_body_timeout忘了client_max_body_size。 ✅ 前者管停顿默认 60s后者管总体积默认 1m。体积超限是立刻返回 413跟超时无关。❌ 用超时参数来给上传限速。 ✅ 这是拿错误工具干正确的事。限速用limit_rate/limit_conn超时只该用来兜异常。❌ 开了proxy_request_buffering off却没有proxy_http_version 1.1。 ✅ 流式转发需要分块传输支持上游通常也需要 HTTP/1.1 才能正确接收不定长的请求体。❌ 开了proxy_request_buffering off却指望失败时能自动重试到另一台上游。 ✅ 请求体已经开始发送无法重放。这个模式的代价之一就是失去重试能力需要应用层做补偿。❌ 改了client_body_temp_path指向一个新目录没建目录也没改属主。 ✅ 目录必须事先存在属主是 worker 运行用户RHEL 系nginxDebian 系www-dataSELinux 下还需要正确的上下文restorecon。否则上传会以 500 失败错误信息跟超时完全不像。❌ 直接在http块把client_max_body_size设成0不限制图省事。 ✅ 这在暴露到公网的站点上等于取消了一道防护。按业务路径单独放开并在边缘设备上做体积与速率限制。总结症状 / 目标先看哪条指令默认值备注413 Request Entity Too Largeclient_max_body_size1m大文件上传第一嫌疑人上传到一半断日志while reading client request bodyclient_body_timeout60s相邻读操作之间的停顿日志while sending request to upstreamproxy_send_timeout60s只在关掉请求缓冲后才是主要约束后端处理慢导致 502/504proxy_read_timeout60s相邻读操作之间的停顿500日志No space left on deviceclient_body_temp_path所在分区—临时目录容量与权限希望降低磁盘占用、更快让上游开始处理proxy_request_buffering offon代价是不能重试、上游要支持分块参数调大的理由代价client_max_body_size允许大文件攻击面变大按路径限制client_body_timeout容忍慢速客户端连接占用更久proxy_send_timeout流式模式下容忍慢速上传上游连接占用更久proxy_read_timeout上游处理大文件慢worker 被占住更久结论上传大文件失败时proxy_send_timeout通常不是答案。先确认client_max_body_size、再看client_body_timeout、然后检查临时目录最后才轮到代理侧的超时而且只有在主动关掉proxy_request_buffering之后proxy_send_timeout才真正开始管住上传速度。搞清楚每个超时管的是哪两个操作之间的间隔这类问题就不会再靠全都调大试试来解决了。
返回列表