单端口部署 FastGPT 与 MinIO:从签名错误、CORS 到文件读取 404 的完整排障实录

发布时间:2026/7/22 6:45:31
单端口部署 FastGPT 与 MinIO:从签名错误、CORS 到文件读取 404 的完整排障实录 本文记录一次 FastGPT v4.14.4 与 MinIO 共用 Nginx 单端口部署时的完整排障过程。最终目标是服务器只对外开放一个端口同时保证 FastGPT 页面、文件上传和文件解析都能正常工作。一、部署背景部署环境存在以下限制FastGPT、MinIO、MongoDB、PostgreSQL 等组件通过 Docker Compose 运行Nginx 与 Docker 位于同一台服务器对外只开放6080端口FastGPT 页面通过http://chat.example.com:6080访问MinIO API 不能直接暴露到公网需要支持浏览器直传文件并让 FastGPT 在问答过程中读取文件内容。最终采用的流量结构如下/fastgpt-public/*/fastgpt-private/*其他路径S3 内部管理请求预签名 URL 文件读取浏览器chat.example.com:6080Nginx :6080MinIO API127.0.0.1:3001FastGPT127.0.0.1:8085fastgpt-minio:9000这里没有采用/minio/...作为对象存储路径而是直接使用 S3 Path Style 的 Bucket 路径/fastgpt-public/... /fastgpt-private/...二、为什么不能简单使用/minio前缀最初的外部地址配置为S3_EXTERNAL_BASE_URL:http://10.0.0.10:6080/minio但 FastGPT v4.14.4 创建 MinIO 外部客户端时只读取 URL 的协议、主机名和端口并不会将 pathname 作为 S3 基础路径使用。核心逻辑可以简化为constexternalBaseURLnewURL(options.externalBaseURL);newClient({endPoint:externalBaseURL.hostname,port:Number(externalBaseURL.port),useSSL:externalBaseURL.protocolhttps:});因此/minio不是可靠的 S3 API 前缀。若再由 Nginx增加或删除该路径预签名 GET/PUT 的 canonical path 还可能发生变化最终导致SignatureDoesNotMatch正确做法是让外部地址只包含协议、域名和端口S3_EXTERNAL_BASE_URL:http://chat.example.com:6080然后在 Nginx 中根据 Bucket 名称路由到 MinIO。相关源码S3BaseBucket三、问题一预签名请求签名不一致最早出现的错误是The request signature we calculated does not match the signature you provided.这类问题通常由以下因素造成FastGPT 与 MinIO 的 Access Key、Secret Key 不一致Region、协议、端口或 endpoint 配置不一致反向代理修改了参与签名的 Host 或请求路径使用了临时凭据却没有传递 session token容器系统时钟错误。需要特别注意将容器时区改为Asia/Shanghai只会改变时间显示方式不会修正系统时钟也不会直接修复签名。本次部署统一使用以下内部配置S3_ENDPOINT:fastgpt-minioS3_PORT:9000S3_USE_SSL:falseS3_ACCESS_KEY:${S3_ACCESS_KEY}S3_SECRET_KEY:${S3_SECRET_KEY}S3_PUBLIC_BUCKET:fastgpt-publicS3_PRIVATE_BUCKET:fastgpt-private同时Nginx 转发 MinIO 请求时使用$http_host保留客户端请求中的端口proxy_set_header Host $http_host;相比之下$host通常不包含非标准端口可能导致预签名请求中的 Host 与 MinIO 实际收到的 Host 不一致。四、问题二浏览器触发 Private Network Access 拦截文件上传地址最初是http://10.0.0.10:6080/fastgpt-private而 FastGPT 页面通过以下地址访问http://chat.example.com:6080浏览器报错The request client is not a secure context and the resource is in more-private address space local.虽然两个地址最终指向同一台服务器但浏览器判断 Origin 时使用的是“协议 主机名 端口”。域名和 IP 不同因此它们属于不同源同时页面通过普通 HTTP 访问更私有的内网 IP触发了 Chrome Private Network Access 限制。解决方法是让 FastGPT 页面和上传 URL 使用完全相同的 OriginS3_EXTERNAL_BASE_URL:http://chat.example.com:6080FE_DOMAIN:http://chat.example.com:6080这样上传 URL 会变成http://chat.example.com:6080/fastgpt-private浏览器不再进行跨域访问也不需要额外添加宽泛的 CORS 响应头。五、问题三FastGPT 容器无法解析外部域名统一域名后生成预签名地址时又出现getaddrinfo EAI_AGAIN chat.example.comFastGPT v4.14.4 会为S3_EXTERNAL_BASE_URL创建一个外部 MinIO Client。生成 POST 或 GET 预签名 URL时这个客户端可能需要通过外部域名查询 Bucket Region。因此域名不仅要能被浏览器解析也必须能被 FastGPT 容器解析。对于较新的 Docker可以使用extra_hosts:-chat.example.com:host-gateway但旧版本 Docker/docker-compose会报错invalid IP address in add-host: host-gateway兼容旧版本的方案是直接映射宿主机内网地址extra_hosts:-chat.example.com:10.0.0.10该配置需要同时添加到可能创建或读取 S3 外部 URL 的服务services:fastgpt:extra_hosts:-chat.example.com:10.0.0.10fastgpt-plugin:extra_hosts:-chat.example.com:10.0.0.10容器重新创建后可以验证dockerexecfastgptgrepchat.example.com /etc/hosts预期结果类似10.0.0.10 chat.example.com六、问题四上传成功但文件内容变成 404解决上传问题后浏览器已经可以成功执行POST http://chat.example.com:6080/fastgpt-private预签名接口也正常返回code: 200、对象 Key、Policy 和签名。但在问答中要求模型读取文件时模型拿到的内容却是Request failed with status code 404这并不是中文编码乱码也不是模型幻觉而是 FastGPT 将文件下载错误文本当成了文件正文。6.1 源码级根因FastGPT v4.14.4 的文件读取流程中存在以下逻辑constparsedURLnewURL(url,http://localhost:3000);if(requestOriginparsedURL.originrequestOrigin){urlurl.replace(requestOrigin,);}constresponseawaitaxios.get(url,{baseURL:serverRequestBaseUrl,responseType:arraybuffer});当页面 Origin 与 MinIO 外部地址相同时requestOrigin http://chat.example.com:6080 fileUrl http://chat.example.com:6080/fastgpt-private/chat/...FastGPT 会把完整 URL 改成/fastgpt-private/chat/...接着 Axios 使用 FastGPT 自身的本地地址作为baseURL实际请求变成http://fastgpt-container:3000/fastgpt-private/chat/...但 FastGPT 的 3000 端口并没有/fastgpt-private路由于是返回 404。异常捕获逻辑又会把错误文本格式化为文件内容最终模型看到的就是Request failed with status code 404相关源码readFiles.ts6.2 Nginx 兼容修复在不重新编译 FastGPT 的情况下可以仅对转发到 FastGPT 的请求清除Originlocation / { proxy_pass http://127.0.0.1:8085; proxy_http_version 1.1; 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; proxy_set_header X-Forwarded-Host $http_host; # 避免 FastGPT v4.14.4 将同源 S3 URL 改写为本地相对路径 proxy_set_header Origin ; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; }注意不要在 MinIO 的两个 location 中清除 Origin。浏览器上传请求仍应原样转发给 MinIO。清除 FastGPT 上游请求的 Origin 是针对 v4.14.4 同源 S3 部署的兼容方案。长期方案应优先考虑升级到已确认修复该逻辑的 FastGPT 版本修改源码使 S3 Bucket 路径不参与同源 URL 的相对化处理为 MinIO 使用独立文件域名并正确配置 HTTPS 与 CORS。七、最终 Docker Compose 核心配置以下配置假设 Nginx 与 Docker 在同一台服务器version:3.3x-share-db-config:x-share-db-configS3_EXTERNAL_BASE_URL:http://chat.example.com:6080S3_ENDPOINT:fastgpt-minioS3_PORT:9000S3_USE_SSL:falseS3_ACCESS_KEY:${S3_ACCESS_KEY}S3_SECRET_KEY:${S3_SECRET_KEY}S3_PUBLIC_BUCKET:fastgpt-publicS3_PRIVATE_BUCKET:fastgpt-privateservices:fastgpt-minio:image:minio/minio:固定版本restart:alwaysports:-127.0.0.1:3001:9000-127.0.0.1:3002:9001environment:TZ:Asia/ShanghaiMINIO_ROOT_USER:${S3_ACCESS_KEY}MINIO_ROOT_PASSWORD:${S3_SECRET_KEY}volumes:-./fastgpt-minio:/datacommand:server /data--console-address :9001healthcheck:test:[CMD,curl,-f,http://localhost:9000/minio/health/live]interval:30stimeout:20sretries:3fastgpt:image:registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.14.4restart:alwaysports:-127.0.0.1:8085:3000extra_hosts:-chat.example.com:10.0.0.10environment::*x-share-db-configTZ:Asia/ShanghaiFE_DOMAIN:http://chat.example.com:6080depends_on:fastgpt-minio:condition:service_healthyfastgpt-plugin:image:fastgpt-plugin:固定版本restart:alwaysextra_hosts:-chat.example.com:10.0.0.10environment::*x-share-db-configTZ:Asia/Shanghaidepends_on:fastgpt-minio:condition:service_healthy将后端端口绑定到127.0.0.1可以避免 Docker 将 3001、3002 和 8085 暴露到所有网卡。部分 Linux 防火墙配置可能被 Docker 的 NAT 规则绕过因此不能只依赖外层防火墙来隐藏这些端口。八、最终 Nginx 6080 配置map $http_upgrade $connection_upgrade { default upgrade; close; } server { listen 6080 default_server; server_name chat.example.com 10.0.0.10; proxy_connect_timeout 60s; proxy_read_timeout 900s; proxy_send_timeout 900s; client_max_body_size 100m; location /fastgpt-public { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; 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; proxy_set_header X-Forwarded-Host $http_host; proxy_request_buffering off; proxy_buffering off; } location /fastgpt-private { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; 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; proxy_set_header X-Forwarded-Host $http_host; proxy_request_buffering off; proxy_buffering off; } location / { proxy_pass http://127.0.0.1:8085; proxy_http_version 1.1; 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; proxy_set_header X-Forwarded-Host $http_host; # FastGPT v4.14.4 同源 S3 读取兼容处理 proxy_set_header Origin ; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }这段配置只展示 6080 服务不需要修改服务器已有的其他端口配置。九、部署与验证9.1 校验并重建容器docker-compose-fdocker-compose.yml configdocker-compose-fdocker-compose.yml up-d--force-recreate fastgpt fastgpt-plugin如果修改了 MinIO 端口映射则应重建全部相关服务docker-compose-fdocker-compose.yml up-d--force-recreate9.2 验证容器内域名解析dockerexecfastgptgrepchat.example.com /etc/hosts9.3 校验并加载 Nginxnginx-tnginx-sreload9.4 验证请求链路FastGPT 首页curl-Ihttp://chat.example.com:6080/MinIO 内部健康状态curl-ihttp://127.0.0.1:3001/minio/health/live私有 Bucket 未签名访问通常会返回 MinIO XML 格式的AccessDenied这反而说明路由已经到达 MinIOcurl-ihttp://chat.example.com:6080/fastgpt-private完整业务验证应满足POST /api/core/chat/presignChatFilePostUrl 200 POST /fastgpt-private 204 或 200 GET /fastgpt-private/chat/object-key?X-Amz-* 200如果最后一个 GET 返回 404检查它是否被转发到了 FastGPT如果返回 S3 XML 错误则检查 Bucket、Key、签名 Host 和请求路径。十、故障现象速查表现象主要原因检查重点SignatureDoesNotMatch凭据、Host、Region 或路径不一致Access Key、Secret Key、$http_host、endpoint浏览器 PNA/CORS 拦截页面使用域名上传使用内网 IPFE_DOMAIN与S3_EXTERNAL_BASE_URL是否同源EAI_AGAINFastGPT 容器无法解析外部域名容器 DNS、extra_hostsinvalid IP address: host-gatewayDocker 版本过旧使用明确的宿主机内网 IP上传成功、读取内容为 404v4.14.4 将同源 S3 URL 改成 FastGPT 本地路径Nginx FastGPT 路由清除上游Origin大文件返回 413Nginx 默认请求体限制过小client_max_body_size十一、生产环境建议启用 HTTPS普通 HTTP 会明文传输登录令牌、文件内容和预签名参数。即使只能开放一个端口也可以直接在该端口终止 TLS。不要使用默认密码将 S3、MongoDB、Redis、FastGPT Root Key 等凭据放入受保护的环境变量或 Secret 管理系统。限制后端端口Nginx 与 Docker 同机时将 FastGPT 和 MinIO 映射到127.0.0.1。固定镜像版本不要在生产环境使用浮动标签升级前先验证文件上传和解析链路。谨慎使用 Origin 兼容方案清除 Origin 是针对 FastGPT v4.14.4 同源 S3 场景的工程性修复。升级版本后应重新验证是否还需要该配置。保留访问日志文件上传问题应同时查看浏览器 Network、FastGPT 日志、Nginx access log 和 MinIO 日志不能只依赖前端报错。十二、总结单端口同时承载 FastGPT 和 MinIO 的关键不是增加一个任意的/minio前缀而是保持 S3 请求路径和 Host 不变并按 Bucket 路径进行路由/fastgpt-public → MinIO /fastgpt-private → MinIO / → FastGPT整个故障链路体现了四个容易混淆的层次S3 签名关注 endpoint、Host、Region 和 canonical path浏览器关注 Origin、CORS 和 Private Network AccessDocker 容器有独立的 DNS 与网络视角FastGPT v4.14.4 的同源 URL 优化会把 S3 文件错误地指向自身服务。只有将浏览器、Nginx、容器 DNS、FastGPT 外部 S3 Client 和 MinIO 实际路径放在同一条请求链路中分析才能解释为什么“预签名成功”“上传成功”和“文件读取成功”是三个彼此独立的阶段。