WSS代理后获取真实IP需Nginx透传X-Forwarded-For和X-Real-IP头并后端主动解析,缺一不可;多级代理时须配置real_ip_from与real_ip_header。

WSS 代理后拿不到真实 IP,不是 WebSocket 协议限制导致的,而是 Nginx 配置漏掉了关键透传头、后端又没主动解析——两者缺一不可。
proxy_set_header 必须同时设 X-Forwarded-For 和 X-Real-IP
只加 proxy_set_header X-Real-IP $remote_addr 不够,WSS 握手是 HTTP 请求,但很多后端框架(如 Spring Boot、FastAPI)默认不读 X-Real-IP,只认 X-Forwarded-For。而如果只加 X-Forwarded-For,又可能被多层代理污染或覆盖。
-
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for:追加而非覆盖,保留原始链路 -
proxy_set_header X-Real-IP $remote_addr:提供单值备用,方便日志或简单校验 -
proxy_set_header X-Forwarded-Proto $scheme:WSS 场景下必须设为wss,否则后端判断协议出错 - 别漏掉 WebSocket 必备三行:
proxy_http_version 1.1、proxy_set_header Upgrade $http_upgrade、proxy_set_header Connection "upgrade"
后端必须手动解析 X-Forwarded-For,不能依赖 remote_addr
Docker 容器或云环境里,request.getRemoteAddr() 或 ws.client.host 拿到的永远是 Nginx 的内网 IP(比如 10.0.1.5),不是客户端真实地址。框架不会自动“降级”去读 header。
- Spring Boot(Tomcat):需在
server.xml中配RemoteIpValve,并指定remoteIpHeader="X-Forwarded-For"和可信代理网段(如10.0.0.0/8) - FastAPI:用
websocket.headers.get("x-forwarded-for"),再按逗号分割取第一个非空项(ip.split(",")[0].strip()) - Node.js(Express + ws):握手时从
req.headers["x-forwarded-for"]取值,注意检查是否为字符串且非空 - Java Servlet:必须用
request.getHeader("X-Forwarded-For"),getRemoteAddr()在代理后完全失效
多级代理时 real_ip_from + real_ip_header 才可靠
如果前端还有 CDN(如 Cloudflare)、LB 或另一层 Nginx,$remote_addr 已经不是客户端 IP,此时仅靠 proxy_set_header 会把上一级代理 IP 当作真实 IP 写入 header,造成污染。
- 在
http或server块中加:set_real_ip_from 192.168.0.0/16(填你可信代理的网段) - 再配:
real_ip_header X-Forwarded-For,让 Nginx 自动用该 header 最左有效 IP 覆盖$remote_addr - 启用
real_ip_recursive on:当 X-Forwarded-For 有多个 IP 时,从右往左跳过已知代理,取第一个非代理 IP - 验证是否生效:在日志中加
$realip_remote_addr变量,比$remote_addr更可信
最容易被忽略的是:WSS 握手请求的 header 传递和普通 HTTP 请求一样,但很多人只测了 REST 接口,忘了在 WebSocket 连接建立瞬间抓包确认 X-Forwarded-For 是否真到了后端。建议用 curl 模拟握手或在后端打印全部 headers,先看这个头有没有、值对不对,再往下调逻辑。











