Nginx 如何正确代理 SSE 与 WebSocket和彻底搞懂Nginx三大指令:location、rewrite、proxy_pass请求转发机制
根据客户端是否发送 Upgrade 头,动态设置 Connection 值# 若是 WebSocket 请求($http_upgrade = "websocket"),则 Connection 设为 "upgrade"# 否则设为 "close",避免普通 HTTP 请求被误判为长连接# 默认:升级连接(用于 WebSocket)'' close;# 空值:关闭连接(用于普通 HTTP)此 map
在现代 Web 应用中,实时通信已成标配。无论是使用 Server-Sent Events (SSE) 实现服务端单向推送,还是通过 WebSocket 建立双向通道.配置不当就会出现莫名其妙的问题。
一、SSE vs WebSocket:选对技术是前提
|
特性 |
SSE(Server-Sent Events) |
WebSocket |
|---|---|---|
|
通信方向 |
服务器 → 客户端(单向) |
双向全双工 |
|
协议基础 |
基于 HTTP/1.1,MIME 类型为 text/event-stream |
独立协议,需通过 Upgrade: websocket 升级建立 |
|
连接管理 |
浏览器自动重连 |
需应用层实现重连 |
|
适用场景 |
实时通知、日志流、行情推送 |
聊天室、协同编辑、在线游戏 |
建议:仅需推数据 → 用 SSE;需双向交互 → 用 WebSocket
前端路由约定:
普通请求:/SSE接口:/sse/...WebSocket:/ws/...
第一步:全局定义 WebSocket 升级映射(必须放在 http {} 块内)
# 根据客户端是否发送 Upgrade 头,动态设置 Connection 值
# 若是 WebSocket 请求($http_upgrade = "websocket"),则 Connection 设为 "upgrade"
# 否则设为 "close",避免普通 HTTP 请求被误判为长连接
map $http_upgrade $connection_upgrade {
default upgrade; # 默认:升级连接(用于 WebSocket)
'' close; # 空值:关闭连接(用于普通 HTTP)
}
此 map 指令不能放在 server 或 location 中!
第二步:Server 块完整配置(含逐行注释)
server {
listen 80;
server_name your-domain.com; # 替换为你的实际域名或 IP
# ───────────────────────────────
# 普通 API 请求(无需特殊处理)
# ───────────────────────────────
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $http_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;
}
# ───────────────────────────────
# SSE 专用配置(关键!务必逐项检查)
# ───────────────────────────────
location ~ ^/sse/ {
# 转发到后端服务
proxy_pass http://127.0.0.1:8000;
# 必须使用 HTTP/1.1(SSE 依赖长连接和分块传输)
proxy_http_version 1.1;
# 【核心】禁用 Nginx 缓冲!
# 默认开启时,Nginx 会缓存整个响应体,导致事件流无法实时到达客户端
proxy_buffering off;
# 禁用代理缓存(防止中间件缓存事件流)
proxy_cache off;
# 关闭 gzip 压缩(SSE 不兼容压缩,浏览器无法解析压缩后的 event stream)
gzip off;
# 清空 Connection 头,防止 Nginx 自动添加 "Connection: close"
proxy_set_header Connection '';
# 设置足够长的超时时间(单位:秒)
# 根据业务需求调整,例如 1 小时 = 3600 秒
proxy_read_timeout 3600s; # 等待后端发送数据的最大空闲时间
proxy_send_timeout 3600s; # 向客户端发送数据的超时
proxy_connect_timeout 3600s;# 与后端建立连接的超时
# 告诉上游代理(如 CDN、多层 Nginx)不要缓冲此响应
proxy_set_header X-Accel-Buffering no;
# 启用分块传输编码(SSE 依赖 chunked encoding 逐块发送数据)
chunked_transfer_encoding on;
# 可选:CORS 支持(允许跨域访问)
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Origin,Authorization,Accept,X-Requested-With' always;
# 处理 CORS 预检请求(OPTIONS)
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 1728000; # 预检结果缓存 20 天
add_header 'Content-Length' 0;
return 204; # 返回空响应,立即结束
}
}
# ───────────────────────────────
# WebSocket 专用配置(核心是协议升级)
# ───────────────────────────────
location ^~ /ws/ {
proxy_pass http://127.0.0.1:8000;
# 必须使用 HTTP/1.1
proxy_http_version 1.1;
# 【核心】传递 WebSocket 升级头
# 将客户端的 Upgrade 和 Connection 头原样转发给后端
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 可选: 传递 Authorization 头部,如果需要身份验证
# proxy_set_header Authorization $http_authorization;
# 常规代理头(用于获取真实 IP、协议等)
proxy_set_header Host $http_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;
# WebSocket 连接通常较长,但无需像 SSE 那么久
proxy_read_timeout 300s; # 5 分钟无消息自动断开(可按需调整)
proxy_send_timeout 300s;
proxy_connect_timeout 3600s;
}
}
替代写法(不使用 map):
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
三、高频问题排查清单
🔸 SSE 连接不工作?
检查
proxy_buffering off;是否遗漏
确认后端返回 Content-Type: text/event-stream
保证gzip off;已设置
多层Nginx代理时,每一层都要配置proxy_buffering off;
WebSocket 连接失败?
检查是否传递了 Upgrade 和 Connection 头:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
客户端开发者工具中查看 WebSocket 请求状态是否为 HTTP 101(Nginx access.log 有时不准确,以浏览器 Network 面板为准)
超时时间是否过短(默认 60 秒,可能不够)→ 建议设为 proxy_read_timeout 3600s;
四、最小可用配置(调试推荐)
SSE
location /sse {
proxy_pass http://backend;
proxy_buffering off; # 必须!
proxy_cache off;
gzip off;
proxy_set_header Connection '';
proxy_http_version 1.1;
proxy_read_timeout 3600s;
}
WebSocket
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
}
总结
SSE 成败关键:proxy_buffering off; + gzip off; + 长超时(proxy_read_timeout)
WebSocket 成败关键:正确透传 Upgrade 与 Connection 头 + proxy_http_version 1.1 + 长超时
配置生效后:别忘了重载 Nginx
sudo nginx -t && sudo nginx -s reload
彻底搞懂Nginx三大指令:location、rewrite、proxy_pass请求转发机制
你是不是也遇到过这样的情况:
改了 location 路由规则,结果静态资源 404;
加了 rewrite,但请求没跳转成功;
配了 proxy_pass,却出现路径重复或缺失;
明明复制了网上的配置,却怎么都不生效。
这不是你一个人的问题。
在 Nginx 的世界里,location、rewrite、proxy_pass 是最常用也最容易混淆的三兄弟。它们掌控着“请求转发”的命脉,却常常因为顺序、作用域、路径拼接等细节让人头大。
本文将从原理到实战,彻底搞清楚这三者之间的区别与联系。看完这篇文章,你将能自信地写出任何一条请求转发规则,不再被神秘的路径匹配坑住。
一、Nginx 的请求处理流程全景图
理解三者的前提,是理解 Nginx 处理请求的顺序。
当浏览器访问一个网站时,请求会经过以下几个阶段:

简单来说,Nginx 在拿到请求后,会:
-
先确定哪个
server块负责处理(根据域名和端口匹配); -
然后在该
server里找到最符合路径的location; -
若有
rewrite指令,则根据规则重写 URI; (URL:http:ip:port/api/v1/lst) (URIapi/v1/lst) -
最后执行
proxy_pass(转发给后端)或root(返回静态资源)。
所以,这三者其实是同一条请求处理链上的不同阶段:
-
location决定“去哪一段配置”; -
rewrite改写“要去的路径”; -
proxy_pass决定“最终送去哪里”。
location [=|~|~*|^~|@] /uri/ {
# 配置内容
}
二、location—Nginx 路由分发的“入口判官”
location 是 Nginx 路由机制的核心,用于匹配请求 URI(即域名后的路径部分)。
1. 基本语法
常见匹配符解释:
|
符号 |
含义 |
匹配特性 |
|---|---|---|
|
= |
精确匹配 |
与 URI 完全相同才命中 |
|
~ |
正则匹配(区分大小写) |
用正则匹配路径 |
|
~* |
正则匹配(不区分大小写) |
常用于文件扩展名匹配 |
|
^~ |
前缀匹配且优先 |
若命中则不再匹配正则 |
|
(无符号) |
普通前缀匹配 |
默认行为 |
举个例子:
server {
listen 80;
server_name example.com;
location = / {
return 200 "首页";
}
location ^~ /static/ {
root /usr/share/nginx/html;
}
location ~* \.(jpg|png|css|js)$ {
expires 30d;
}
}
这段配置表示:
-
访问
/返回首页; -
访问
/static/下的路径返回静态文件; -
匹配
.jpg.css.js的文件启用缓存。
2. 匹配优先级规则(非常关键)
Nginx 在匹配时遵循以下顺序:
-
精确匹配(
=); -
前缀匹配(
^~); -
正则匹配(
~或~*),按定义顺序; -
普通前缀匹配(最长匹配)。
这解释了很多“配置不生效”的原因。 例如:
location / {
return 200 "Default";
}
location /api/ {
return 200 "API";
}
location ^~ /api/private/ {
return 200 "Private";
}
访问 /api/private/test,结果返回 "Private",因为 ^~ 优先级高于 /api/。
三、rewrite—URL 改写的“化妆师”
rewrite 是修改 URI 的工具,它可以改变用户请求的路径,然后重新交给 Nginx 匹配。
1. 基本语法
rewrite regex replacement [flag];
参数解释:
-
regex:匹配规则,使用正则;
-
replacement:重写后的新路径;
-
flag:控制行为,如
last、break、redirect、permanent。
2. 常见 flag 对比
|
flag |
含义 |
说明 |
|---|---|---|
|
last |
停止当前匹配,重新匹配新的 URI |
常用于内部跳转 |
|
break |
停止 rewrite,不再重写,也不重新匹配 |
常用于当前 location 内的替换 |
|
redirect |
临时重定向(302) |
浏览器地址栏会变 |
|
permanent |
永久重定向(301) |
浏览器缓存重定向结果 |
3. 举例说明
server {
listen 80;
server_name example.com;
location /old/ {
rewrite ^/old/(.*)$ /new/$1 permanent;
}
}
访问 http://example.com/old/test.html浏览器会被重定向到 http://example.com/new/test.html。
如果改成:
rewrite ^/old/(.*)$ /new/$1 last;
则是内部跳转,浏览器地址栏不会变化,但实际响应来自 /new/ 路由。
四、proxy_pass—请求转发的“幕后搬运工”
proxy_pass 用于把请求转发到后端服务,是反向代理的核心。
1. 基本语法
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
当访问 /api/user 时,请求被转发到:
http://127.0.0.1:8080/user
2. 重点!路径拼接规则
很多人被坑在这一点上。proxy_pass 的行为取决于结尾是否带 /。
|
配置写法 |
请求 |
|---|---|
proxy_pass http://127.0.0.1:8080; |
http://127.0.0.1:8080/api/user |
proxy_pass http://127.0.0.1:8080/; |
http://127.0.0.1:8080/user |
是不是很细?这差一个 / 就会完全改变结果。
原理是:
-
不带斜杠 → Nginx 会把匹配到的
location前缀拼接在目标 URL 上; -
带斜杠 → Nginx 会用重写后的路径替换整个请求 URI。
3. 常见示例
反向代理后端 API:
location /api/ {
proxy_pass http://backend/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
转发静态文件服务:
location /static/ {
proxy_pass http://127.0.0.1:9000/;
}
代理 WebSocket:
location /ws/ {
proxy_pass http://127.0.0.1:7000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
五、三者协作的典型场景:一次完整的请求转发
让我们看一个实际项目配置:
server {
listen 80;
server_name www.demo.com;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
请求:http://www.demo.com/api/user/info
处理流程如下:
-
server_name匹配成功; -
命中
location /api/; -
rewrite将/api/user/info→/user/info; -
break终止 rewrite,继续执行当前location; -
proxy_pass将请求转发到http://127.0.0.1:8080/user/info; -
后端服务返回响应。
理解这条链路后,你再也不会被转发逻辑绕晕。
六、debug 技巧:遇到转发问题怎么查?
如果遇到转发异常、路径重复、404,可用以下手段定位:
-
打印 rewrite 日志在
nginx.conf中打开:
rewrite_log on;
error_log /var/log/nginx/error.log notice;
-
使用 curl 观察请求
curl -v http://localhost/api/test
-
用 uri / $proxy_host 变量打印日志
log_format debug '$remote_addr "$request_uri" -> "$uri" proxy:$proxy_host';
access_log /var/log/nginx/debug.log debug;
这些方法能帮助你看到每一步到底发生了什么重写和转发
七、建议:写出清晰、稳定的转发配置
-
尽量避免嵌套 rewrite,多用明确路径;
-
确认 proxy_pass 末尾
/是否符合预期; -
正则 location 放在文件最后;
-
static 与 api 分开独立配置;
-
若有多后端服务,用 upstream 管理:
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
proxy_pass http://backend;
这样可以实现高可用与负载均衡。
总结
location、rewrite、proxy_pass 并不是孤立的配置项,而是 Nginx 控制请求流的三重奏:
-
location决定“进入哪条支线”; -
rewrite决定“路径怎么改”; -
proxy_pass决定“最终去哪”。
它们共同构建出一套声明式的请求分发 DSL(领域特定语言),是 Nginx 的灵魂所在。 当你能流畅地在脑中推演一条请求的流向时,你就真正理解了 Nginx 的精髓。
配置文件不只是写给机器的命令,它是系统架构思维的具象化。 理解转发机制,就像理解一座城市的交通图——掌握了流向,才能掌控性能与稳定。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)