apache反向代理websocket需启用mod_proxy_wstunnel模块,proxypass必须用ws://或wss://协议前缀,统一路径末尾斜杠,配置timeout和proxytimeout延长超时,并在负载均衡时通过stickysession=routeid实现会话粘滞。

Apache反向代理WebSocket时连接被意外关闭
Apache默认不识别Upgrade头,会把WebSocket的Connection: upgrade和Upgrade: websocket当作普通HTTP请求处理,导致握手失败、连接秒断。关键不是配转发规则,而是让Apache明确“这是WebSocket,别按HTTP流程走”。
必须启用mod_proxy_wstunnel模块(Apache 2.4.5+内置),并确保mod_proxy和mod_proxy_http已加载。检查命令:httpd -M | grep proxy,输出中应含proxy_wstunnel_module。
- 若未启用,编辑
httpd.conf或mods-enabled/proxy_wstunnel.load,添加LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so - 重启前务必执行
apachectl configtest,否则mod_proxy_wstunnel缺失会导致启动失败且错误日志只报“Invalid argument”这类模糊提示
ProxyPass配置必须用wss://或ws://协议前缀
很多用户照搬HTTP反向代理写法,用ProxyPass /ws/ http://backend:8080/ws/,这会让Apache走mod_proxy_http而非mod_proxy_wstunnel,结果是握手成功但后续帧传输异常(如客户端收不到ping响应、连接静默断开)。
正确写法必须显式指定ws://或wss://协议:
ProxyPass /ws/ ws://127.0.0.1:8080/ws/ ProxyPassReverse /ws/ ws://127.0.0.1:8080/ws/
- 路径末尾的
/必须一致,否则Apache可能重写Sec-WebSocket-Location头出错,导致浏览器拒绝连接 - 若后端是HTTPS WebSocket(wss),前端也必须是HTTPS站点,否则现代浏览器直接阻止混合内容;此时
ProxyPass仍写wss://,但Apache自身需配置SSL证书(不能只靠后端提供) - 不要加
retry=0之类参数——mod_proxy_wstunnel不支持ProxyPass的多数HTTP参数,加了反而触发配置校验失败
超时设置不当导致长连接被强制中断
WebSocket长连接依赖TCP保活,但Apache默认Timeout(全局)和ProxyTimeout(代理级)都设为60秒,一旦连接空闲超过该值,Apache会主动断开socket,后端无法感知,表现为“连接突然消失”。
需在虚拟主机或目录配置中显式延长:
Timeout 3600 ProxyTimeout 3600
-
Timeout影响整个Apache进程的请求等待时间,设太高可能增加DoS风险;建议仅在WebSocket专用vhost里覆盖,而非全局修改 -
ProxyTimeout只对mod_proxy系模块生效,但mod_proxy_wstunnel实际使用的是底层socket超时,因此必须同时设这两个值 - Linux系统级TCP keepalive(
/proc/sys/net/ipv4/tcp_keepalive_time)建议调至1200秒(20分钟),避免内核先于Apache断连
负载均衡场景下Session粘滞(sticky session)不可省略
WebSocket连接建立后,所有帧都发往同一后端节点。若用ProxyPass直连单台,没问题;但用ProxyPass /ws/ balancer://wscluster/做集群时,Apache默认轮询,第二次ping帧可能落到另一台没该连接状态的机器上,直接丢弃。
必须启用基于ROUTE参数的粘滞策略,并要求后端在WebSocket握手响应中注入Set-Cookie: ROUTEID=...;或返回Cookie头:
ProxyPass /ws/ balancer://wscluster/ stickysession=ROUTEID
- 后端应用需在HTTP Upgrade响应头中添加
Set-Cookie: ROUTEID=abc123; Path=/ws/; HttpOnly,且值需全局唯一(如用节点名+随机数) - Apache不会自动解析WebSocket帧里的session信息,只认HTTP阶段的cookie或URL参数(如
?ROUTEID=xxx),所以必须在握手完成前就传递 - 若后端无法改代码,可用
mod_headers在Apache层强行注入,但要注意时机——必须在ProxyPass之后、响应发出前操作,否则无效
真正麻烦的不是配置几行指令,而是WebSocket的生命周期横跨HTTP握手和二进制帧传输两个阶段,Apache只在第一个阶段可控,后面全靠TCP层维持。任何一环的超时、代理缓冲、中间设备reset,都会让连接无声失效,而错误日志里往往只有“connection reset by peer”这种废话。










