在现代 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 在拿到请求后,会:

  1. 先确定哪个 server 块负责处理(根据域名和端口匹配);

  2. 然后在该 server 里找到最符合路径的 location

  3. 若有 rewrite 指令,则根据规则重写 URI; (URL:http:ip:port/api/v1/lst) (URIapi/v1/lst)

  4. 最后执行 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 在匹配时遵循以下顺序:

  1. 精确匹配(=);

  2. 前缀匹配(^~);

  3. 正则匹配(~ 或 ~*),按定义顺序;

  4. 普通前缀匹配(最长匹配)。

这解释了很多“配置不生效”的原因。 例如:

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:控制行为,如 lastbreakredirectpermanent

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 的行为取决于结尾是否带 /

配置写法

请求 /api/user 实际转发地址

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

处理流程如下:

  1. server_name 匹配成功;

  2. 命中 location /api/

  3. rewrite 将 /api/user/info → /user/info

  4. break 终止 rewrite,继续执行当前 location

  5. proxy_pass 将请求转发到 http://127.0.0.1:8080/user/info

  6. 后端服务返回响应。

理解这条链路后,你再也不会被转发逻辑绕晕。

六、debug 技巧:遇到转发问题怎么查?

如果遇到转发异常、路径重复、404,可用以下手段定位:

  1. 打印 rewrite 日志在 nginx.conf 中打开:

rewrite_log on;
error_log /var/log/nginx/error.log notice;
  1. 使用 curl 观察请求

curl -v http://localhost/api/test
  1. 用 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;

这样可以实现高可用与负载均衡。

总结

locationrewriteproxy_pass 并不是孤立的配置项,而是 Nginx 控制请求流的三重奏:

  • location 决定“进入哪条支线”;

  • rewrite 决定“路径怎么改”;

  • proxy_pass 决定“最终去哪”。

它们共同构建出一套声明式的请求分发 DSL(领域特定语言),是 Nginx 的灵魂所在。 当你能流畅地在脑中推演一条请求的流向时,你就真正理解了 Nginx 的精髓。

配置文件不只是写给机器的命令,它是系统架构思维的具象化。 理解转发机制,就像理解一座城市的交通图——掌握了流向,才能掌控性能与稳定。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐