WebSocket跨域必须在握手阶段通过CORS响应头校验解决:浏览器在Upgrade请求中严格检查Access-Control-Allow-Origin等头,Nginx需用map动态白名单+add_header注入合法头,并拦截OPTIONS防止穿透,确保101响应前头正确且无冲突。

WebSocket 跨域不是靠“放行响应体”解决的,而是必须在初始 HTTP 握手阶段就通过 CORS 头完成校验。Nginx 本身不处理 WebSocket 协议,但它必须正确透传或注入关键响应头,确保浏览器在发送 Upgrade: websocket 请求时能通过预检(如果需要)并接受 101 响应。
明确 WebSocket 握手阶段的 CORS 规则
浏览器对 WebSocket 连接只做一次跨域检查:即发起 GET 请求、携带 Upgrade: websocket 和 Origin 头时。此时若服务端返回的响应中缺少合法的 Access-Control-Allow-Origin,或值为 * 但同时要求凭据(如 Cookie),连接就会被静默拒绝,控制台报错类似 WebSocket connection to 'wss://...' failed。
- 只要前端代码中指定了
credentials: true(例如new WebSocket(url, { protocols: [], ... })隐式带 cookie),后端响应头中的Access-Control-Allow-Origin就不能是*,必须精确匹配$http_origin -
Access-Control-Allow-Credentials: true必须与非通配符的Origin同时存在,否则浏览器忽略该头 -
Sec-WebSocket-Key和Sec-WebSocket-Accept是协议必需头,由后端生成;Nginx 不得修改或覆盖它们
用 map + add_header 实现动态 Origin 白名单
避免硬编码,把可信前端域名集中管理。在 http{} 块顶部添加:
map $http_origin $cors_origin {
default "";
"~^https?://(app|dashboard|staging)\.example\.com$" "$http_origin";
"~^https?://localhost:3000$" "$http_origin";
}
然后在 WebSocket 代理的 location 中使用:
location /ws/ {
proxy_pass http://backend_ws;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
<pre class="brush:php;toolbar:false;"># 只有白名单来源才加 CORS 头
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Sec-WebSocket-Key, Origin" always;
# 对 OPTIONS 预检请求快速响应(虽 WebSocket 握手一般不发 OPTIONS,但某些客户端或网关会发)
if ($request_method = "OPTIONS") {
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
return 204;
}
}
不转发 OPTIONS 到后端,也不让后端参与握手校验
WebSocket 握手是标准 HTTP 请求升级流程,不该由后端业务逻辑处理。Nginx 必须拦截并终结所有 OPTIONS 请求,防止它穿透到后端造成 405 或 500 错误,干扰升级流程。
- 确保
proxy_pass不命中OPTIONS—— 用if提前 return 是最稳妥方式 - 不要依赖后端返回 CORS 头:一旦 WebSocket 连接建立,后续帧通信不再走 HTTP,CORS 失效;只有握手那一刻有效
- 若后端本身也输出 CORS 头,需在 Nginx 层用
always参数强制覆盖,避免重复或冲突
验证与调试要点
直接 curl 模拟握手请求,比看浏览器控制台更可靠:
curl -i \ -H "Origin: https://app.example.com" \ -H "Upgrade: websocket" \ -H "Connection: Upgrade" \ -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \ -H "Sec-WebSocket-Version: 13" \ https://your-domain.com/ws/
观察返回中是否包含:
HTTP/1.1 101 Switching ProtocolsAccess-Control-Allow-Origin: https://app.example.comAccess-Control-Allow-Credentials: true- 且没有
Access-Control-Allow-Origin: *与credentials: true并存











