Nginx 这层反向代理配置本身并不复杂真正复杂的是它背后牵扯到的整套系统资源管理。我见过太多团队在 Nginx 配置上反复打磨却忽略了操作系统层面的参数瓶颈结果压测一上来就莫名其妙地出现大量连接失败。今天这篇文章我想把 Nginx 反向代理配置和系统参数配置这两件事放在一起讲分享一些我在实际项目中总结的经验和踩过的坑。我把这篇文章拆成了几个部分先讲清楚反向代理在真实场景里到底解决了什么问题然后给出一个可以直接用的 Nginx 配置清单并解释每个参数的目的接着重点讲 Linux 系统参数配置——这部分往往是被忽略的关键最后分享一次完整的排查过程让大家对配置出问题时的定位思路有直观感受。1. 为什么需要反向代理从直连应用到统一入口1.1 直连模式下的真实痛点先说一个我印象很深的场景。某个团队上线了一个 Web 服务开发阶段一切正常但一旦放开公网访问各种奇怪的问题就来了用户上传大文件经常中断、跨地域访问时延很高、偶尔还会出现连接直接被重置的情况。排查了很久发现应用本身没有崩溃但客户端就是连不上或者连上了很快就断开。这里面的核心原因其实很典型客户端直连后端应用时所有网络层面的挑战都由应用自己承担。比如并发连接数高时单台机器的文件描述符有限比如慢客户端弱网用户长时间占用连接导致后端线程池耗尽比如后端应用对外暴露真实 IP既容易受到扫描攻击又难以做灵活的流量调度。我在看问题的时候习惯先问一句在这个架构里Nginx 到底承担了什么角色答案是它可以成为一个统一的流量入口把面向客户端的复杂性和后端应用的稳定性隔离开。1.2 反向代理带来的三层价值第一层连接收敛。客户端直接连后端时每个客户端 IP 都会与后端建立独立的 TCP 连接。通过 Nginx 做反向代理后客户端只与 Nginx 建立连接而 Nginx 和后端之间可以保持少量长连接从而减少后端的连接创建和销毁开销。第二层能力前置。SSL 终止、Gzip 压缩、静态资源缓存、请求体大小限制、限流、超时控制等都可以放在 Nginx 这一层统一处理后端应用只关注业务逻辑。第三层故障隔离。当后端某台机器出现异常时Nginx 可以通过健康检查提前摘除节点当某个客户端行为异常时比如大量慢速请求可以在网关层将其终止避免拖垮后端。打个比方直连模式就像所有人都直接涌进一间办公室找一个办事员队伍一乱整个办公室就瘫痪而 Nginx 像是门口的前台先把你分类、登记、疏导再决定叫谁出来响应你。前台的效率决定了整栋楼的接待能力这也是为什么系统参数配置如此重要。2. 反向代理配置清单每个参数都要知道为什么2.1 基础配置框架下面是一个我在生产环境里常用的反向代理配置框架已经去掉业务相关的内容只保留通用骨架。这不是唯一写法但每个参数我都加了注释说明它的目的。upstream backend_svc { server 10.0.1.10:8080 max_fails3 fail_timeout10s; server 10.0.1.11:8080 max_fails3 fail_timeout10s; keepalive 32; } server { listen 80; server_name example.internal; location /api/ { proxy_pass http://backend_svc; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 5s; proxy_send_timeout 30s; proxy_read_timeout 30s; proxy_buffer_size 16k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k; } }2.2 proxy_pass 的路径拼接规则很多新手在配置proxy_pass时会困惑一两个问题结尾加不加/以及 upstream 名称之后的内容如何与 location 匹配拼接。规则其实很简单如果proxy_pass后面是不带 URI 的地址例如http://backend_svc;那么客户端请求的原始 URI 会被完整转发到后端。如果proxy_pass后面是带 URI 的地址例如http://backend_svc/;那么 location 匹配到的部分会被替换为/后面的路径再进行拼接。举个例子请求/api/user/listlocation /api/ { proxy_pass http://backend_svc; # 后端收到 /api/user/list } location /api/ { proxy_pass http://backend_svc/; # 后端收到 /user/list }这个差异如果没搞清楚经常会遇到后端返回 404 或者接口路径不对。我通常在配置里让proxy_pass不带 URI保持路径逐字透传这样排查问题时不至于多一层心智负担。2.3 Header 透传与协议相关参数proxy_set_header Host $host;这行很重要。很多后端应用会根据 Host 头做域名路由或安全校验如果不显式透传Nginx 默认会用 upstream 的地址作为 Host后端可能返回 400 或走错虚拟主机。另外我强烈建议加上X-Forwarded-Proto否则后端在处理 HTTPS 请求重定向时可能会错误地生成 HTTP 链接。再来看代理协议版本的问题。Nginx 与后端通信时默认使用 HTTP/1.0这样不够好原因在于 HTTP/1.0 不支持 Keep-Alive 的持久连接语义。打开上游 keepalive 的时候必须配合proxy_http_version 1.1; proxy_set_header Connection ;这里将 Connection 头清空是为了避免客户端与 Nginx 之间的 Connection 头被透传到后端干扰后端的连接管理。生产环境中上游 keepalive 能把后端从频繁的 TCP 握手和 TIME_WAIT 中解放出来对于短时高频请求的提升非常显著。2.4 超时参数与 buffer 参数超时参数我建议按不同场景分别设置proxy_connect_timeout是建立上游连接的超时通常 3-5 秒就够如果后端处理慢但网络没问题这个参数不需要太大。proxy_read_timeout是等待后端响应体的超时这个要根据业务处理时间留出余量但也不要给到 300 秒否则慢接口会占住 Nginx worker 的连接资源。我在压测接口时一般从 10s 开始调试再结合业务耗时慢慢放宽。proxy_send_timeout是 Nginx 向后端发送请求体的超时上传大文件场景尤其重要。Buffer 参数经常被误解。proxy_buffer_size用于存储后端返回的响应头如果响应头里包含了较大的 Cookie 或自定义 Header默认 4k 可能不够出现 502 错误时值得检查这一项。而proxy_buffers则决定了 Nginx 在把响应转给客户端之前可以缓冲多少数据。合理的缓冲能避免慢客户端拖住后端太久。我之前遇到过一次很隐蔽的问题后端返回的一个 Header 太大导致客户端收到 502但后端日志却显示请求正常完成。后来用curl -v一测发现后端实际返回的 Header 接近 8KB而默认的 buffer size 只有 4KB。调整proxy_buffer_size后问题直接消失。3. 系统参数配置文件描述符与内核网络栈的调整这一部分往往是最容易被忽略的。很多同学只盯着 Nginx 配置却忘了 Nginx 跑在操作系统上所有的 TCP 连接、文件句柄、内存缓冲都受系统限制。如果不做系统参数配置哪怕 Nginx 配置写得再完美高并发下依然会撞墙。3.1 文件描述符限制在 Linux 中一切皆为文件网络连接也被抽象为文件描述符。Nginx worker 进程能打开的最大文件数受到两个限制操作系统级的ulimit -n以及 Nginx 配置中的worker_rlimit_nofile。我建议在/etc/nginx/nginx.conf主配置块中加一行worker_rlimit_nofile 65535;同时修改系统的 limits.confcat /etc/security/limits.conf EOF * soft nofile 65535 * hard nofile 65535 EOF有人问只改一个行不行实测下来两个都得配合好。Nginx worker 进程的文件描述符限制由worker_rlimit_nofile决定但该值不能超过系统的 hard limit。如果你只在 Nginx 里加大了数字但系统层没放行最终生效的还是那个较小的值。验证方式也很简单nginx -T | grep worker_rlimit_nofile cat /proc/worker_pid/limits | grep open files通过查看 worker 进程实际的 limits就可以确定配置是否真正生效。3.2 内核网络栈TIME_WAIT 与端口范围Nginx 作为反向代理既要和客户端建立连接也要和后端建立连接。当并发请求比较高时每个请求都可能产生一个新的上游连接。在高频短连接的场景下被动关闭的一端会产生大量 TIME_WAIT 状态的连接这些连接会占用本地端口和连接表项。对应到内核参数我通常会关注这几个net.ipv4.ip_local_port_range 1024 65535 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_fin_timeout 30 net.core.somaxconn 4096 net.ipv4.tcp_max_syn_backlog 8192ip_local_port_range表示系统可以自动分配的本地端口范围。默认值在不同发行版上可能不同有的甚至只到 32768。如果 Nginx 和后端的连接数超过这个范围就会报Cannot assign requested address错误这个错我在压测时见过不止一次。tcp_tw_reuse设置为 1 的含义是允许内核在安全前提下复用 TIME_WAIT 状态下的连接用于新建连接。这个参数和SO_REUSEADDR不是一回事它是在发起连接的一方生效的。对于 Nginx 作为客户端去连接后端的场景这个参数非常实用。tcp_fin_timeout决定 TIME_WAIT 连接等待回收的时间。默认是 60 秒调低到 30 秒能更快释放连接资源。不过我不建议把它调得太极端比如改成 5 秒可能导致部分场景下旧包判定异常。3.3 进程连接队列与 listen backlog还有一个容易出问题的参数是net.core.somaxconn。它的作用是限制 listen socket 的 accept 队列长度。Nginx 的listen指令里也有一个backlog参数但实际生效值受系统somaxconn的限制。如果并发连接快速增加客户端建立 TCP 连接后但 Nginx 来不及 accept这些连接会堆积在队列里。队列满了之后新连接在客户端表现为握手没有响应或者在 Nginx 日志中出现 connection reset by peer。配置时两个地方要同时改echo 4096 /proc/sys/net/core/somaxconnserver { listen 80 backlog4096; }这里面的原则很简单backlog 的上限由内核参数决定Nginx 配置的值超过系统上限时会被静默截断。所以调整监听队列时先确认内核参数再改 Nginx。3.4 参数持久化与生效验证不建议直接用sysctl -w改完就完事那样重启后丢失。应该在/etc/sysctl.conf中统一写入cat /etc/sysctl.conf EOF net.ipv4.ip_local_port_range 1024 65535 net.ipv4.tcp_tw_reuse 1 net.ipv4.tcp_fin_timeout 30 net.core.somaxconn 4096 net.ipv4.tcp_max_syn_backlog 8192 EOF sysctl -p验证是否生效sysctl net.ipv4.ip_local_port_range sysctl net.core.somaxconn我在做系统参数配置时有一个习惯每改完一组参数就把当前值记录下来写在部署文档里。这样几周后如果线上行为有变化能快速想起当时到底调过什么。4. 反向代理场景下的常见故障与排查链路纯理论讲完了我想通过一次真实的排障经历来展示配置出问题时是怎么一步步定位的。4.1 现象压测时大量 502 与偶发连接失败有次我参与一个模拟项目X的压测压测工具产生的并发请求在 2000 左右时一开始表现得很好跑了三五分钟后突然出现成片的 502 和少量connection refused。当时团队的第一反应是后端服务挂了。但查看后端监控CPU 才 40%内存也没满。于是排查顺序被拉长了。那段时间我们试过加机器、调 upstream 权重、修改 Nginx 的proxy_read_timeout但都没有明显效果。直到有人翻到系统日志发现大量 Cannot assign requested address这才把视线拉回到本地端口耗尽的问题上。4.2 定位过程从 Nginx 日志到内核计数器遇到 502 时我第一个动作是看 Nginx error logtail -f /var/log/nginx/error.log同时关注两个地方client timed out和connect() failed (99: Cannot assign requested address)。出现后者基本说明 Nginx 作为客户端向某个后端发起连接时本机无法分配新的临时端口。验证的方法是用ss查看 TIME_WAIT 连接数量ss -s ss -tan state time-wait | wc -l在压测进行中TIME_WAIT 数量往往已经达到几万个。结合ip_local_port_range的值计算一下可用的临时端口数65535 - 1024 1大约六万多个。但注意这些端口还要被其他出站连接共用如果 TIME_WAIT 没有快速回收新连接自然无端口可用。我们的解决方案是一是开启tcp_tw_reuse二是把tcp_fin_timeout从 60 调到 30三是确实扩大ip_local_port_range的上限。调整之后TIME_WAIT 数量下降非常明显压测直到 4000 并发都没有再出现这个问题。4.3 另一个坑超时设置的连锁反应还有一次是内部服务之间调用变慢但并没有报错。查了上游应用耗时接口平均才 80ms但从外部看有的请求要 2 秒。后来发现问题出在proxy_read_timeout和上游 keepalive 的配合上。当时 upstream 里配了keepalive 32但 Nginx 的proxy_http_version还是默认的 1.0导致 keepalive 根本没有生效。每个请求都在重新建立连接加上 TLS 握手耗时整体延迟就上去了。这个案例提醒我upstream 特性必须与协议版本配合使用配置不是写了就生效而是要有体系地验证。4.4 故障排查速查表我把常见现象、可能原因和对应检查点整理了一下现象可能原因检查点大量 502后端没起来 / connect timeoutupstream状态、后端端口监听connection refused端口范围耗尽 / 后端拒绝连接ip_local_port_range、后端连接数偶发超时慢客户端 / 后端处理时间长proxy_read_timeout、后端日志客户端收不到响应就断开buffer 不足 / Header 过大proxy_buffer_size、curl -v 查看响应头压测初期正常中后期变慢TIME_WAIT 过多 / 端口枯竭ss -s观察连接状态重启后参数失效sysctl.conf 未持久化sysctl -p验证这张表本身就是我的排查模板。每次遇到反向代理稳定性问题先按这个顺序过一遍通常能很快缩小范围。5. 配置管理的实际建议从单机到集群的思考5.1 配置文件拆分与命名规范在单机 Nginx 上配置很简单但一旦涉及多台代理机器配置管理就变得很重要。我的习惯是把 Nginx 配置文件按功能拆开nginx.conf只保留全局配置和 http 块。conf.d/default.conf放 server 配置。conf.d/upstreams/*.conf按业务拆分 upstream 定义。对 upstream 定义使用 include 引用例如include conf.d/upstreams/*.conf;这样每新增一个服务只需要新增一个文件不影响现有配置。配合配置管理工具比如 Ansible、SaltStack 等可以把传播配置的成本降到最低。5.2 reload 与 restart 的选择每次改配置后都要执行nginx -t nginx -s reload这看起来是标准操作但还是有人在不该 restart 的时候直接 restart导致连接被粗暴切断。reload是平滑行为master 进程会启动新的 worker 并逐步替换旧的 worker旧连接可以在优雅退出流程中完成。如果配置有语法错误nginx -t会提前报错相关操作也不会执行。5.3 记录基线参数我一直建议团队维护一张系统参数基线表记录每台代理机的内核参数、Nginx worker 数量、worker_connections、worker_rlimit_nofile等。这样发生问题后两三分钟就能判断是本机配置漂移了还是流量真的变大了。举个例子某次线上延迟突然升高大家排查了应用、网络、数据库最后发现是有同事在某台机器上执行了系统优化脚本把net.core.somaxconn改回了 128。没有基线表的话这种问题几乎不可能快速定位。5.4 关于 worker 数量的建议Nginx 的worker_processes auto通常就能给出合理配置它会读取 CPU 核心数。但对于大量 I/O 操作的代理场景worker 数不宜超过 CPU 核心数太多。之前我会误导性地以为 worker 越多并发越好实测中发现 worker 数超过核心数后上下文切换开销反而成为瓶颈。配合worker_connections的设置Nginx 每个 worker 可以管理的连接数通常是一两千超过太多会导致 epoll 事件循环负担加重。配置公式可以简化为理论最大并发数 worker_processes × worker_connections。但这是理论值实践里由于内核参数限制实际能达到的并发远低于这个数字所以不要盲目把worker_connections调到 65535。6. 关于系统参数配置的边界思考6.1 为什么说 TCP 调优不是万能的有些团队遇到性能问题第一时间就把tcp_tw_reuse、tcp_max_syn_backlog全部调高看起来系统开放了很多但效果未必好。TCP 调优的本质是在连接复用和资源回收之间找平衡而不是一味调大数字。举例来说tcp_tw_reuse1在绝大多数情况下是安全的但如果你用了 NAT 或者负载均衡设备并且同一组四元组被复用时存在旧数据包干扰的话极端场景可能出现连接异常。实际部署时我建议先开启并观察再决定是否长期保留。6.2 看清瓶颈之后再调参有一次帮朋友排查 Nginx 代理大文件传输缓慢的问题一开始以为是 TCP 缓冲区太小调了tcp_rmem、tcp_wmem效果不明显。后来抓住一个关键现象Nginx 在向客户端发送响应时CPU 占用非常高。进一步用 perf 分析才发现瓶颈在连接加密解密上不是网络栈。所以调参之前先确认瓶颈层到底在哪。可以通过top、iftop、nginx -T等快速判断是 CPU 密集、网络带宽受限还是文件描述符耗尽。系统参数配置解决的是操作系统资源限制问题它不能凭空提升网络带宽也不能替代应用层优化。6.3 参数变更的灰度意识即使是系统参数也不能直接在业务高峰期全局调整。我建议先在金丝雀机器上验证观察几分钟内的连接成功率、延迟分位数、TIME_WAIT 数量变化确认无误后再同步到其他机器。涉及内核参数的变更务必保证可以快速回滚改回旧值并执行sysctl -p。7. 经验总结从配置到问题的完整视角在最后我想分享几点实践中沉淀下来的想法也当是给后来人少走弯路的提示。配置 Nginx 反向代理时不要只盯着location、proxy_pass这些核心指令系统参数的上下边界决定了配置能否真正发挥效果。同一套 Nginx 配置在一台默认参数的机器上能撑住 800 并发在优化过内核参数的机器上可能稳定支持 3000 并发差别就是这么实际。排查代理问题时推荐这个顺序先看操作系统的连接状态再看 Nginx 错误日志最后才看业务代码。很多问题表面上是代码问题实际是底层资源到了上限浪费了大量开发时间。最后再提一个小技巧做压测时可以用ss -tan | awk {print $1} | sort | uniq -c快速查看当前连接状态分布每过几秒记录一次。如果 TIME_WAIT 数量持续上升且不回收基本就是端口耗尽的前兆系统参数的调整方向也就明确了。Nginx 反向代理和系统参数配置的价值恰恰体现在这些看不见的细节里只有在高并发条件下你才会真正感受到它们的分量。