本文记录一次在 Docker + Nginx 反向代理 场景下,部署 Open WebUI + Ollama 时,遇到 WebSocket 400 与前端解析异常的问题,以及完整的定位与修复过程。本文采用工程记录风格,省略与具体环境强绑定的细节(路径、域名、IP 已脱敏),可作为同类问题的参考模板。


一、问题现象

部署完成后,表现为:

  • Web 页面可正常打开(HTTP 200)
  • 但 WebUI 日志中持续出现:
GET /ws/socket.io/?EIO=4&transport=websocket 400
  • 浏览器控制台或前端报错:
Unexpected token 'd', "data: {..." is not valid JSON

直观感受是:

  • UI 能加载
  • 但实时通信 / 流式输出异常
  • 日志中大量 400,看起来像是服务未正确工作

二、初步排查:确认不是服务本身的问题

1. 确认容器状态

  • Ollama 容器正常运行
  • Open WebUI 容器正常运行
  • 容器内部网络通信正常(WebUI 可访问 Ollama)

2. 直接访问容器端口

通过本机或内网直接访问 WebUI 暴露端口:

  • 返回 HTML
  • 日志正常

说明:

问题不在 Open WebUI 或 Ollama 本身


三、确认问题边界:反代之后才出问题

当通过 Nginx 反代访问时:

  • 页面依然 200
  • /ws/socket.io 请求在 WebUI 日志中返回 400
  • 前端不断重试该 websocket 请求

结论很明确:

问题发生在 Nginx → Open WebUI 的反向代理层


四、问题本质分析

1. Open WebUI 使用了什么通信机制?

  • WebUI 使用 Socket.IO / Engine.IO
  • 表现为访问路径:/ws/socket.io
  • 本质依赖 WebSocket(以及其握手流程)

2. 为什么会 400?

常见原因:

  • Nginx 没有正确转发 Upgrade / Connection
  • WebSocket 请求被当作普通 HTTP GET 转发
  • 后端(uvicorn / FastAPI)无法完成 websocket 握手,返回 400

此外,如果开启了:

  • proxy_buffering on
  • gzip

还可能破坏:

  • SSE
  • 流式输出

从而导致前端出现:

Unexpected token 'd'

(前端以 JSON 解析流式 data)


五、修复思路

核心原则只有三条:

  1. WebSocket 请求必须正确 Upgrade
  2. 普通 HTTP 请求不要被强制 upgrade
  3. 关闭 Nginx buffering / cache / gzip

工程上最稳妥的做法是:

为 WebSocket 单独拆一个 location


六、Nginx 配置修复方案(核心)

1. 在 http{} 中定义 upgrade map

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

2. server 块中拆分 WebSocket 与普通请求

server {
    listen 443 ssl http2;

    # WebSocket / Socket.IO
    location /ws/ {
        proxy_pass http://<webui_backend>;
        proxy_http_version 1.1;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host       $host;

        proxy_read_timeout 3600;
        proxy_send_timeout 3600;

        proxy_buffering off;
        proxy_cache off;
        gzip off;
    }

    # 普通 HTTP / SSE / 静态资源
    location / {
        proxy_pass http://<webui_backend>;
        proxy_http_version 1.1;

        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host       $host;

        proxy_buffering off;
        proxy_cache off;
        gzip off;

        proxy_read_timeout 3600;
        proxy_send_timeout 3600;
    }
}

关键点:

  • /ws/ 强制 upgrade
  • / 根据请求头动态决定是否 upgrade

七、验证方式(避免被旧日志误导)

1. 只看最近日志

docker logs --since 2m open-webui

重点观察:

  • 是否还在持续刷 /ws/socket.io ... 400

2. 检查 Nginx access log

grep socket.io access.log | tail

3. 功能验证(最重要)

  • WebUI 页面正常
  • 对话可创建
  • 模型回复为流式输出

如果以上三点成立:

即可认定配置已经稳定


八、关于“残留 400 日志”的说明

在排查过程中,容易出现一种错觉:

以为问题还存在,其实只是翻到了旧日志

原因:

  • 浏览器在早期配置错误时频繁重试 websocket
  • 修复后日志并不会自动消失

正确做法永远是:

只看最近 1~2 分钟的日志


九、总结

本次问题的核心结论:

  • Open WebUI 本身没有问题
  • Ollama 本身没有问题
  • 问题完全出在 Nginx 反向代理对 WebSocket / 流式请求的处理方式

经验要点:

  • WebSocket 必须单独对待
  • 不要在 location / 里无脑 Connection: upgrade
  • SSE / 流式输出一定要关 buffering
  • 排错时务必区分“旧日志”和“当前状态”

该配置在长期运行、低配置主机、无 GPU 环境下均表现稳定,可作为 Open WebUI + Ollama 的反代参考模板。

Leave a Reply

Your email address will not be published. Required fields are marked *