ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Nginx WebSocket代理配置实战:从原理到排错

Nginx WebSocket代理配置实战:从原理到排错 开 头做Web开发这些年WebSocket的代理配置几乎是每个后端工程师都会撞上的一道坎。别看它平时不显眼一旦线上出现“连接总是断开”“升级失败返回400”“聊天消息发不出去”这类问题十有八九都出在Nginx这层。WebSocket和普通HTTP最大的区别就是它是长连接一旦中间隔了一个Nginx如果Nginx不配合做协议升级后端连接根本建立不起来。所以这篇博文围绕“Nginx如何配置WebSocket代理”这件事把原理、最小可用配置、生产环境改造、排错技巧和调优经验全部梳理一遍适合正在接手带实时通信功能项目的开发者也适合刚入门Nginx但对WebSocket概念模糊的运维同学参考。1. WebSocket代理的核心逻辑与配置思路1.1 HTTP升级机制Nginx必须在握手阶段放行WebSocket连接并不是独立的TCP协议它借用了HTTP的Upgrade机制完成握手。客户端发来的请求头里会带Upgrade: websocket和Connection: Upgrade服务端响应101 Switching Protocols之后双方才正式切换成WebSocket长连接。Nginx默认会把请求当作普通HTTP处理收到Upgrade头时并不会自动转发给后端。它需要显式地照着规范把这两个头原样传给上游后端才可能返回101。这就是为什么网上所有的Nginx WebSocket配置里都会出现这两行proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade;这里有个很微妙的点Connection头不能简单地写成固定值Upgrade。因为并非所有请求都带Upgrade如果后端还承载普通HTTP接口无脑把Connection改成Upgrade反而会让普通请求出现问题。所以更常见的做法是先定义一个map变量根据请求头里是否有upgrade来决定Connection的值。这就是大量配置里connection_upgrade这个变量的由来。1.2 为什么要经过代理端口收敛、流量入口和故障隔离有人会问WebSocket应用直接把端口暴露出去不是更省事吗项目里加Nginx代理通常不是为了添乱而是为了统一入口。业务服务器可能有多台端口也各不相同对外只暴露80/443一个口子通过域名和location路径把流量分流到对应后端这是最常见的管理模式。另外如果WebSocket服务后面要接国内外多个云节点或者后端会滚动重启Nginx还能做简单的负载均衡和故障摘除。虽然WebSocket长连接需要的粘性比较高但经过代理后动态调整后端节点、做证书卸载、加访问限制都变得容易很多。所以Nginx代理在这里不是可选项几乎是所有生产部署的标准姿势。1.3 选Nginx而不直接裸连的理由很多语言框架本身支持WebSocket比如Node.js的ws库、Python的websockets库它们自己也能建立连接。但实际线上绝对不止一个WebSocket服务在跑可能有实时的消息推送、在线协同、数据看板好几个模块。如果每个模块都开独立端口运维端口管理就是一场灾难。Nginx统一收敛端口后除了代理WebSocket还能顺手处理静态文件、普通API、HTTPS终止、限流。我们的经验是除非整个应用只有WebSocket一个功能否则Nginx这层代理是性价比最高的选择。它帮我们把各种协议的流量理清楚后端服务只需要专注业务逻辑即可。2. 上手实操Nginx配置WebSocket代理的完整步骤2.1 环境准备与版本要求先说版本Nginx从1.3.13开始就支持WebSocket反向代理所以只要是近十年的Nginx版本基本都没问题。但低版本对HTTP/2和WebSocket同时开启支持不太友好建议在生产环境使用1.18以上版本用起来更省心。我这里用一个典型的开发环境做示例后端WebSocket服务监听在本机的8080端口Nginx监听80端口域名先用ws.example.com占位实际调试时可以用127.0.0.1配hosts来模拟。确认Nginx版本的命令很简单nginx -v如果低于1.3.13建议直接升级别在旧版本上浪费时间。2.2 最小可用配置模板这是Nginx配置WebSocket代理时最经典的最小模板我至今还在用只是根据需求加一些参数。先把这块拷到你的nginx配置里试通再说map $http_upgrade $connection_upgrade { default upgrade; close; } upstream ws_backend { server 127.0.0.1:8080; } server { listen 80; server_name ws.example.com; location / { proxy_pass http://ws_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; 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_read_timeout 60s; proxy_send_timeout 60s; } }看懂这段配置WebSocket代理的基本盘就拿下了。下面逐行拆一下它的用意。2.3 核心字段逐一拆解为什么这么写先看map块。这段代码的意思是如果请求头里带了Upgrade那connection_upgrade就取值为upgrade如果没带就取值为close。这样普通HTTP请求和WebSocket请求走同一个配置也不会互相干扰。网上很多教程省略map直接写死Connection Upgrade结果普通的POST请求到了后端会变得异常就是这个细节闹的。再看proxy_http_version 1.1。WebSocket握手要求HTTP/1.1Nginx默认往上游发的是HTTP/1.01.0不支持Upgrade机制所以必须显式改成1.1。这一行漏掉后端的握手必然失败。然后是proxy_set_header Upgrade $http_upgrade注意它用的是$http_upgrade这个Nginx内置变量取值来自客户端请求头。这里必须动态传值不能写死因为普通HTTP请求没有这个头动态传值可以完美保留不同协议的场景差异。Host $host这行也容易忽略。如果不设置Nginx默认会用proxy_pass里的主机名去填充Host头也就是ws_backend后端服务拿到的Host就不是真实域名了有些框架做域名校验或日志分析时就会出问题。最后是proxy_read_timeout和proxy_send_timeout。Nginx默认的代理超时时间是60秒但WebSocket是长连接可能几分钟甚至几小时不通信。如果沿用默认值一旦后端超过60秒没返回数据Nginx就会主动掐断连接。所以需要把超时时间调长比如3600秒或者根据业务链路最长静默时间设定。纯聊天应用心跳间隔通常30秒调3600秒绰绰有余。2.4 配置生效与验证握手是否成功配置写完先检查语法再重载nginx -t nginx -s reload验证能不能真的代理成功不要只用浏览器打开页面说“能访问就行”。WebSocket握手是否成功要在后端日志里看有没有返回101也可以用命令行工具连接测试curl -i -N \ -H Connection: Upgrade \ -H Upgrade: websocket \ -H Host: ws.example.com \ -H Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ \ -H Sec-WebSocket-Version: 13 \ http://127.0.0.1/一条curl命令能帮你快速判断Nginx有没有正确转发Upgrade请求。如果返回的是101 Switching Protocols说明代理链路已经通了如果返回404或400那就要回到配置里找问题。另一个更省事的方案是用websocat工具但我通常直接用这条curl验证不引入额外依赖也行。用浏览器调试的话打开DevTools的Network面板WebSocket请求会单独分类点击可以查看握手请求和响应头。看到101就说明通了。2.5 踩坑经验location路径别随意配我见过不少人把WebSocket代理放在location /ws/下结果前端实际连接写的是wss://domain/导致握手请求发到/而不是/ws/路由根本匹配不上。这里的关键是location里的路径必须和前端WebSocket连接URL的路径一致。如果业务要求连接地址是/socketNginx就配location /socket是/就配location /别想当然。还有一点Nginx的location匹配遵循前缀最长匹配原则。比如同时存在location /和location /ws连接/ws/chat会优先匹配/ws。如果遇到代理没生效先用nginx -T查看实际加载的配置确认请求落到了哪个location里。3. 生产环境改造从能跑到能用3.1 关闭代理缓冲让消息即时推送Nginx默认会缓冲上游返回的数据等攒够一包再发给客户端。对于WebSocket这种实时性要求高的协议缓冲反而造成不必要的延迟。而且WebSocket是全双工通信两边随时都在互相发消息Nginx的proxy_buffering在长期连接下容易导致内存积压。生产环境建议在location块里显式关闭缓冲location / { proxy_buffering off; proxy_cache off; }实测下来加上这两行后消息推送的延迟明显降低尤其是在后端频繁推送小数据包比如行情行情、通知提醒的场景里效果非常明显。3.2 会话粘性负载均衡下如何保证连接稳定WebSocket只是建立了一次TCP连接但业务层往往带有状态。比如在线聊天用户登录后服务端在内存里维护了会话如果Nginx把同一个用户的不同请求分发到不同后端节点业务上就会乱套。Nginx做负载均衡时有几种方式处理这个问题。最简单的是ip_hash按客户端IP计算哈希值同一个IP固定打到同一个后端upstream ws_backend { ip_hash; server 10.0.0.1:8080; server 10.0.0.2:8080; }但ip_hash有个缺陷如果后端节点数量变化上线或摘除同一IP的请求可能被重新映射到其他节点长连接就会断开。更稳妥的做法是在应用层实现会话同步后端节点之间共享会话状态这样即使Nginx转发到不同节点也能正常工作。如果条件允许优先做会话同步而不是依赖Nginx的粘性策略。还有一种least_conn策略适合长连接负载不均的场景它会优先把新请求转发给当前活跃连接最少的后端。对WebSocket这种每个连接都长时间占用的协议来说least_conn比ip_hash更科学但结合业务状态时可能还需要配合sticky模块使用。我这里给出一个带sticky的配置示例它通过Nginx生成的cookie来固定连接Nginx Plus或openresty自带该模块开源版需要编译时启用upstream ws_backend { sticky cookie srv_id expires1h; server 10.0.0.1:8080; server 10.0.0.2:8080; }这个方案对应用层最透明cookie一设会话自然固定到具体节点不需要后端做任何改动。核心思路是代理层的粘性只是兜底方案真正的长期稳定还得靠后端实现无状态或会话共享。3.3 同域名多服务共存API和WebSocket一起代理线上很多项目是同一个域名下既有REST API又有WebSocket接口。这时候路由设计要清晰比如API走/api/WebSocket走/ws/两个location分开配置。下面是我在实际项目中用过的同域名双location配置server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/certs/example.com.crt; ssl_certificate_key /etc/nginx/certs/example.com.key; location /api/ { proxy_pass http://api_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 普通HTTP接口不需要Upgrade相关头 } location /ws/ { proxy_pass http://ws_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_buffering off; } }这样配置的好处是前端只需要统一用wss://example.com/ws和https://example.com/api端口统一、证书统一、跨域问题也少很多。SSL终止由Nginx完成后端服务直接监听HTTP即可不用每个后端都配置证书。但要注意WebSocket前端如果用的是wss://Nginx上必须开启SSL并正确配置证书否则浏览器会直接拒绝连接。3.4 容器化场景上游地址如何写现在服务基本都跑在Docker里了Nginx容器和后端容器在同一网络下时upstream里的server地址不能写127.0.0.1要写容器名或服务名。比如docker-compose里services: nginx: image: nginx:1.24 ports: - 80:80 networks: - app-net ws_app: image: myapp networks: - app-netNginx配置文件里的upstream要对应写成upstream ws_backend { server ws_app:8080; }在Docker环境里配Nginx还有一个老生常谈的问题Nginx容器reload之后DNS解析可能不会自动更新。如果你动态修改过容器的IP地址Nginx可能还在连旧的IP。解决办法是让Nginx容器和上游容器使用同一个Docker网络并设置resolver指令配合set变量动态解析上游地址或者干脆重启Nginx容器让连接重置。3.5 日志配置连接断开时能查到原因调试WebSocket问题第一件事就是看Nginx日志。默认的access_log只记录请求行根本看不出握手是否成功、连接什么时候断开。生产环境务必配置详细日志格式log_format wslog $remote_addr - $remote_user [$time_local] $request_method $request_uri $status $http_upgrade $http_user_agent upstream_addr$upstream_addr upstream_status$upstream_status request_time$request_time body_bytes_sent$body_bytes_sent; server { ... access_log /var/log/nginx/ws_access.log wslog; error_log /var/log/nginx/ws_error.log warn; }如果连接断开了重点看错误日志里的upstream prematurely closed connection或read timed out这类日志能直接告诉你问题出在Nginx和后端之间的链路还是客户端和Nginx之间的链路。我自己排错时90%的问题靠这两份日志就能锁定方向远比抓包来的高效。4. 常见问题与排查实战记录4.1 握手失败返回400 Bad Request怎么办第一次配WebSocket代理时最容易碰到的错误就是客户端打开连接直接报400 Bad Request。原因基本都是Nginx没转对Upgrade头或Connection头。排查步骤很简单用上面的那条curl命令发一个带Upgrade头的请求看Nginx返回什么。如果返回400先检查自己的配置里proxy_http_version 1.1有没有写proxy_set_header Upgrade $http_upgrade有没有写。如果两个都写了还报400再检查是不是map块没定义好导致$connection_upgrade变量为空。我曾经因为把map写到了另一个配置文件里而那个文件没被include结果$connection_upgrade一直是空字符串Nginx直接报错。另外注意Nginx配置中map块必须放在http层不能放到server或location里否则语法错误或变量不生效。4.2 连接建立后几秒就被断开这种问题有一个典型特征浏览器能连上但每隔一小会儿就断开重连。如果断开间隔正好是60秒那基本可以断定是proxy_read_timeout或proxy_send_timeout默认值60秒在作怪。WebSocket空闲时如果没有心跳包Nginx就认为连接无数据流动到超时时间直接掐断。解决方案有两个层面第一层Nginx把超时时间调大比如proxy_read_timeout 3600s。但这只是治标不治本如果业务本身设计有静默期建议从前端加心跳机制每隔30秒发一个ping帧既保活连接也让Nginx感知到流量。前后端配合起来连接才能稳定挂一天不脱落。第二层调整Nginx的keepalive_timeout参数。这个参数控制Nginx与客户端之间空闲连接的超时时间。注意它是HTTP keepalive的参数和WebSocket的长连接并不完全一样但同样会影响连接生命周期。生产环境我一般设置keepalive_timeout 65s略大于前端心跳间隔预防临界情况。4.3 到了凌晨连接就大量断开有一段时间客户反馈每天晚上两点左右WebSocket连接集体断开。排查发现是因为Nginx配置了每天凌晨的重载任务重载后旧的worker进程要退出正在处理的长连接也被干净利落地关闭。这里面有个容易忽略的机制Nginx平滑重载会保留一部分连接缓冲但WebSocket长连接通常无法无缝迁移到新worker进程。所以如果业务需要在低峰期更新配置最好配合后端的断线重连机制让客户端在连接断开后自动重连。最终我们做的处理是把Nginx的reload从定时任务里去掉改成人肉操作只在真正的配置变更时reload。同时后端WebSocket服务补上了完善的断线重连逻辑客户端对瞬时断开的容忍度大大提高。4.4 后端日志显示连接正常但客户端推送失败Nginx日志、后端日志都显示连接正常但客户端收不到推送消息。这类问题隐蔽性很强有一次我们调了很久才发现是因为Nginx打开了gzip压缩它对WebSocket帧进行了压缩而客户端的WebSocket库不支持压缩扩展导致消息解析失败。解决办法是给WebSocket代理的网络路径关闭gziplocation /ws/ { gzip off; ... }另外proxy_buffering如果开着也可能导致推送的数据积压在Nginx缓冲区里不往下发。这个前面提过生产环境务必关掉。4.5 前端连的是wssNginx证书校验失败海量问题集中在net::ERR_CERT_COMMON_NAME_INVALID这类证书错误上。排查思路很简单证书的CN或SAN字段必须与客户端访问的域名匹配。如果证书是给example.com签发的但客户端连的是ws.example.com那必然校验失败。解决方法是换匹配域名的证书或者让客户端使用相同域名连接。如果只是为了本地调试可以在Nginx配置里临时关闭SSL验证但生产环境千万别这么干。还有一种情况是客户端缺少根证书。自签名证书在浏览器和操作系统里都不被信任需要手动导入。如果是企业内部系统可以考虑把自签名证书安装到所有客户端的信任库如果是公网服务直接用Lets Encrypt免费证书省心得多。4.6 WebSocket多级代理经过CDN时容易卡住项目里如果套了CDNCDN节点对WebSocket的支持参差不齐。有些CDN默认不转发Upgrade头导致客户端连CDN节点时握手就失败。解决方法是确认CDN厂商支持WebSocket并在CDN配置里显式开启。如果没有特殊需求把WebSocket连接的域名直接解析到源站不走CDN是最省事的路子。我在一个项目里遇到过CDN把WebSocket正常转发但回源时用HTTP/2导致后端收到的Upgrade头格式不对。最终处理是让CDN回源强制走HTTP/1.1问题才解决。这种多层代理的链路中每层都要检查协议版本和头转发策略。5. 性能、监控与安全加固的实践经验5.1 worker进程与连接数上限的调优WebSocket是长连接单个客户端会长时间占用一个连接连接数上限比普通HTTP场景更容易触顶。Nginx的worker_connections决定每个worker进程能同时处理的连接数如果预估客户端同时在线1万个建议配worker_processes auto; events { worker_connections 10240; use epoll; }注意worker_connections是每个worker进程的连接数不是全局的。假设4个worker进程每个10240全局就能处理四万个连接。实际还要给普通HTTP流量留出余量不要卡得太死。另外系统层面的调整也不能漏。文件描述符限制默认可能是1024WebSocket连接一多就报错。生产环境建议把ulimit -n调到65535以上并同步修改/etc/security/limits.conf。Nginx的worker_rlimit_nofile指令也要设置worker_rlimit_nofile 65535;5.2 keepalive连接复用减少上游建连开销Nginx和后端之间的TCP连接复用在WebSocket场景下比较特殊因为WebSocket长连接本身就很难复用但HTTP层面的keepalive可以帮普通API分担一部分压力。在upstream块里加keepalive参数可以让Nginx与后端保持一部分空闲连接减少频繁建连的开销upstream ws_backend { server 127.0.0.1:8080; keepalive 64; }不过要注意WebSocket握手成功后该连接就进入长连接状态不再参与keepalive复用池。所以这个参数对WebSocket本身的收益有限主要是让同一个后端节点上的普通API请求受益。如果后端全是WebSocket服务keepalive设置反而意义不大可以忽略。5.3 限制访问与连接频率WebSocket服务对外暴露后容易被扫描和恶意连接。Nginx层可以做基础防护限制单个IP的连接数限制握手频率。limit_conn_zone $binary_remote_addr zonews_conn:10m; limit_req_zone $binary_remote_addr zonews_req:10m rate10r/s; server { location /ws/ { limit_conn ws_conn 20; limit_req zonews_req burst5; # 其他代理配置 } }limit_conn限制的是同一IP并发连接数limit_req限制的是握手请求频率。WebSocket握手请求本身很轻量但绝对数量一多Nginx的CPU和内存就会吃紧。这两个限制能挡住绝大多数低水平攻击。需要注意的是如果用户IPv6普及程度高$binary_remote_addr对IPv6取到的地址长度不一样可能导致zone大小不够。实际部署时建议根据情况调整zone空间或者按IP类型分别定义。5.4 鉴权前置握手阶段就把非法请求挡在门外WebSocket协议在握手阶段可以通过HTTP头传递token或cookieNginx可以用简单的if判断做一层前置鉴权。比如校验是否存在特定Headerlocation /ws/ { if ($http_sec_websocket_protocol !~* ^chat\.v1$) { return 403; } ... }上面的判断其实有点简陋实际项目更常见的是让客户端在查询参数里带tokenNginx用map或者auth_request去校验。完整方案是用auth_request请求一个认证接口location /ws/ { auth_request /auth; proxy_pass http://ws_backend; ... } location /auth { internal; proxy_pass http://auth_service/check_ws_token; proxy_pass_request_body off; proxy_set_header Content-Length ; proxy_set_header X-Original-URI $request_uri; }auth_request的子请求模式是生产环境里最常用也最稳妥的方案。校验失败返回401Nginx直接拒绝升级后端服务完全不用感知非法流量。但要注意auth_request每来一次握手就发一个子请求对认证服务的压力会放大最好给认证服务加上缓存或限流。5.5 监控连接状态别等用户投诉才发现服务挂了WebSocket服务不像HTTP那样每个请求都有明确的成败状态连接挂没挂只有客户端感知最清楚。Nginx的监控维度我一般看三点request_time异常升高、upstream_status出现500或502、Nginx的active connections数量波动异常。有条件的项目建议把Nginx的stub_status模块打开location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; }配合Prometheus抓取指标可以实时看到Active connections、Waiting、Writing等数值。对WebSocket服务来说Writing这个数字尤其重要它代表正在向客户端写数据的连接数。如果Writing长时间居高不下说明后端推送的积压严重数据发不出去得赶紧查后端消费速度。5.6 常见问题速查表现象可能原因排查/处理手段连接返回400HTTP版本不是1.1或Upgrade头没转确认proxy_http_version 1.1与Upgrade头设置客户端握手成功但立刻断开Websocket服务端主动关闭检查后端日志确认业务层是否拒绝连接连接坚持60秒左右断开超时时间太短调大proxy_read_timeout或前端加心跳浏览器报ERR_CERT_COMMON_NAME_INVALIDSSL证书域名不匹配更换与访问域名匹配的证书偶发断连后端没日志Nginx重载或worker进程退出避免频繁reload前端设计重连机制通过CDN连接失败CDN不支持/没开启WebSocket确认CDN功能必要时让WebSocket直连源站后端收不到推送消息代理缓冲没关或gzip干扰设置proxy_buffering off、gzip off大量连接建立时报EMFILE文件描述符上限不足调worker_rlimit_nofile与系统ulimit6. 备选方案与扩展讨论6.1 openresty的优势场景如果项目里不止是转发还要在Nginx层做请求改写、流量染色或自定义鉴权可以考虑换成OpenResty。它在Nginx基础上集成了Lua脚本能力我一个项目里用它做了按渠道动态调整WebSocket心跳间隔的逻辑用原生Nginx写起来会非常痛苦。用OpenResty做WebSocket代理的大体配置和Nginx基本一样因为核心模块是共通的。但OpenResty可以让工程师在access_by_lua阶段直接读取WebSocket握手请求的Header、客户端IP、Cookie等信息做出更灵活的鉴权和限流策略。适合那种鉴权逻辑经常变又不想反复改Nginx配置重载的场景。6.2 多域名多证书的WebSocket部署业务大了之后不可能所有WebSocket都挂在一个域名下。比如IM、工单提醒、日志推送是不同的子域名证书也要分开。Nginx 1.19以后支持动态证书加载配置一个server块内通过变量选择证书文件减少重复配置。但更稳妥的仍然是分别定义server每块独立配置证书和location逻辑一目了然。server { listen 443 ssl; server_name im.example.com; ssl_certificate /etc/nginx/certs/im.example.com.crt; ssl_certificate_key /etc/nginx/certs/im.example.com.key; location /ws/ { ... } } server { listen 443 ssl; server_name notify.example.com; ssl_certificate /etc/nginx/certs/notify.example.com.crt; ssl_certificate_key /etc/nginx/certs/notify.example.com.key; location /ws/ { ... } }多域名部署时别忘了检查证书的SAN字段是否覆盖所有域名否则又回到ERR_CERT_COMMON_NAME_INVALID的老问题。6.3 与云负载均衡组合时的注意事项如果前面还有云厂商的负载均衡比如阿里云SLB或腾讯云CLB那么Nginx虽然是链路的另一端但它和云LB之间也存在代理关系。云LB默认可能把HTTP/2打开了而WebSocket在HTTP/2中的支持场景更复杂。最简单的稳妥配置是云LB到Nginx这段、Nginx到后端这段全部统一走HTTP/1.1。多一层代理就多一层变量链路越统一排查越简单。还有一个典型的坑云LB的会话保持会话粘滞默认基于IP但客户端经过IPv6 NAT后源IP并不能作为可靠标识。此时需要在云LB上配置基于Cookie的会话保持并把proxy_set_header Cookie $http_cookie和proxy_pass_header Set-Cookie都配好防止cookie在Nginx这层被吞掉。7. 再次总结我的实操体会配置WebSocket代理这种事看起来只是Nginx里的几行配置但真正在生产环境跑顺手需要把超时、粘性、缓冲、日志、证书、心跳这些细节点全部想清楚。我在项目里大概踩过三轮坑第一轮是纯转发能连上就以为完事了第二轮开始处理超时和粘性问题解决断连和负载不均第三轮才把监控、限流、鉴权这些安全手段补齐让WebSocket服务真正敢对外公开。给后来者一个建议WebSocket代理的配置不要照抄先弄懂每一行的含义再结合自己的业务场景做取舍。比如心跳周期是30秒还是2分钟直接决定Nginx的read_timeout该设多大后端节点是否会滚动发布决定要不要用ip_hash依赖Nginx的粘性还是要把会话逻辑下沉到应用层。任何生产配置脱离了业务场景都有可能变成另一场事故的源头。最后分享一个排错技巧遇到诡异的WebSocket断连问题别急着怀疑网络或中间设备先用tcpdump在Nginx入口和出口各抓一次包对比两端的时间戳和序列号。抓包结果能清清楚楚地告诉你连接是在哪个环节断的。Nginx日志、后端日志、客户端日志这“三方日志”对不上时抓包是最后的决定性证据。掌握这个思路WebSocket代理层面的问题基本都能给你快速排查干净后面的路就好走了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进