
前言在开发智能交通与车联网大模型系统平台时前端采用开源chatgpt-web前端框架开发环境使用 Vite生产环境通过 Nginx 部署。一次后端算法服务调整之后出现了一个非常典型的问题本地调用http://localhost:1002/api/gpt/chat正常部署到服务器后调用http://itsgpt.net.cn/api/gpt/chat却返回 404。刚开始看起来像是“前端接口地址写错了”但实际排查后发现问题涉及Vite Proxy、Nginx location 匹配、proxy_pass 路径转换、后端端口变化、Nginx 实际运行配置以及 127.0.0.1 的含义。本文把这次问题从头到尾记录下来不只解决当前问题也整理成一套以后遇到类似“本地正常、生产环境 404”时可以直接套用的排查流程。一、问题背景为什么本地正常服务器却 4041.1 项目运行环境本次项目采用chatgpt-web前端框架进行二次开发主要技术栈包括VueViteAxiosNginxJava 后端大模型 / 算法服务前端开发环境运行在http://localhost:1002核心聊天接口/api/gpt/chat因此本地完整请求地址为http://localhost:1002/api/gpt/chat浏览器开发者工具显示请求方法POST 状态码200 OK也就是说本地接口完全正常。但是部署到服务器后请求变成http://itsgpt.net.cn/api/gpt/chat结果POST 404 Not Found这时候最容易产生一个误区“本地接口能通说明前端代码没问题所以服务器 404 应该是后端的问题。”实际上不能直接这么判断。因为本地请求和生产环境请求根本不是同一条网络链路。1.2 本地请求为什么能正常项目的vite.config.ts中配置了server:{host:0.0.0.0,port:1002,proxy:{/api:{target:http://192.168.110.90:9012,changeOrigin:true,rewrite:pathpath.replace(/api/,/),},},}这里最重要的是proxy和rewrite本地请求/api/gpt/chat经过 Vite 代理后实际上会被转发为192.168.110.90:9012/gpt/chat也就是说本地浏览器看到的是localhost:1002/api/gpt/chat但真正接收请求的后端实际上是192.168.110.90:9012/gpt/chat所以本地请求成功并不能证明 Nginx 配置正确。1.3 打包之后Vite Proxy 还存在吗不存在。这是本次问题最关键的知识点之一。下面这段server:{proxy:{/api:{target:http://192.168.110.90:9012}}}只对npmrun dev启动的Vite 开发服务器生效。执行npmrun build之后Vite 最终只负责生成静态资源例如dist/ ├── index.html ├── assets/ │ ├── xxx.js │ └── xxx.css └── ...部署到服务器之后请求链路变成浏览器 ↓ itsgpt.net.cn ↓ Nginx ↓ 后端服务此时已经没有 Vite Dev Server 参与。所以生产环境接口能否访问最终取决于 Nginx 是否正确转发到了后端。二、服务器排查从 8090 到 9012 找到真正的问题2.1 原来的 Nginx 配置到底转发到了哪里最初的 Nginx 配置中存在location /api/gpt/chat { proxy_pass http://127.0.0.1:8090/gpt/chat; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header Authorization $http_authorization; proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; proxy_connect_timeout 60s; proxy_read_timeout 300s; proxy_send_timeout 300s; }这意味着/api/gpt/chat ↓ Nginx ↓ 127.0.0.1:8090/gpt/chat问题也很快出现了。在服务器执行curl-ihttp://127.0.0.1:8090/gpt/chat返回curl: (7) Failed to connect to 127.0.0.1 port 8090: Connection refused继续测试 POSTcurl-i-XPOST http://127.0.0.1:8090/gpt/chat依然Connection refused这说明一个非常重要的问题Nginx 配置的 8090 端口上已经没有服务在监听。所以生产环境请求itsgpt.net.cn/api/gpt/chat实际上还在尝试访问已经失效的127.0.0.1:8090这就是为什么服务器请求会出现异常。2.2 通过ss查看服务器到底运行了哪些端口服务器排查服务时不要首先猜端口直接执行ss-lntp本次服务器关键结果如下0.0.0.0:8080 0.0.0.0:8081 0.0.0.0:8083 *:9012其中8080 → nginx 8081 → python 8083 → python 9012 → java尤其值得注意*:9012 users:((java,pid246728,fd45))这直接说明9012 端口当前确实有 Java 后端服务正在监听。而之前配置中的8090根本没有出现在监听列表中。因此可以确定原配置 /api/gpt/chat → 8090 ❌ 当前后端 9012 → Java ✅2.3 8081 和 8083 分别是什么服务通过端口只能知道“谁在监听”还需要继续确认具体程序。执行ps-fp301524得到root 301524 ... python /home/LLM/main.py因此8081 → /home/LLM/main.py继续查看 8083ps-fp2223508得到root 2223508 ... /root/miniconda3/envs/llm/bin/python /home/LLM/main-openai.py因此8083 → /home/LLM/main-openai.py可以进一步通过tr\0 /proc/301524/cmdline和tr\0 /proc/2223508/cmdline确认进程实际启动命令。这一步的意义在于不要因为服务器上有很多 Python 服务就直接认为某个端口是当前大模型接口。必须结合端口 PID 启动命令 实际接口测试一起判断。2.4 9012 才是当前正确的 Java 后端服务器执行ss-lntp|grep9012得到LISTEN ... *:9012 ... users:((java,pid246728,fd45))说明当前 Java 服务监听9012对应服务器公网地址118.31.19.221:9012因此当前系统后端应该理解为118.31.19.221:9012但这并不意味着 Nginx 配置中必须写proxy_pass http://118.31.19.221:9012/;如果 Nginx 和 Java 后端就在同一台服务器上更合适的配置是proxy_pass http://127.0.0.1:9012/;三、真正理解 Nginx为什么之前/api/gpt/chat会走 80903.1location 和location ^~到底有什么区别原配置同时存在location /api/gpt/chat { proxy_pass http://127.0.0.1:8090/gpt/chat; }以及location ^~ /api/ { proxy_pass http://127.0.0.1:9012/; }很多人看到/api/gpt/chat以/api/开头就认为应该走9012实际上不是。Nginx 中location /api/gpt/chat这里的表示精确匹配。而location ^~ /api/属于前缀匹配。Nginx 官方文档明确说明location 是精确 URI 匹配一旦找到精确匹配就会结束 location 搜索。([Nginx][1])因此/api/gpt/chat ↓ location /api/gpt/chat ↓ 8090而不是/api/gpt/chat ↓ location ^~ /api/ ↓ 9012所以之前配置实际上是/api/gpt/chat → 8090 其他 /api/* → 9012这正是本次问题的关键。3.2 为什么删除location /api/gpt/chat既然当前 Java 后端已经统一运行在9012那么继续保留location /api/gpt/chat { proxy_pass http://127.0.0.1:8090/gpt/chat; }反而会造成错误。正确做法是删除这段特殊配置让/api/gpt/chat统一进入location ^~ /api/最终形成/api/gpt/chat ↓ location ^~ /api/ ↓ 127.0.0.1:9012 ↓ /gpt/chat这样配置逻辑更加清晰也避免以后后端端口变化时留下旧配置。3.3proxy_pass最后的/为什么重要最终配置location ^~ /api/ { proxy_pass http://127.0.0.1:9012/; }注意9012/ ↑ 这里有一个 /这个/会影响 Nginx 转发后的 URI。例如前端请求/api/gpt/chat配置location ^~ /api/ { proxy_pass http://127.0.0.1:9012/; }最终后端收到/gpt/chat也就是/api/gpt/chat ↓ 删除 /api/ ↓ /gpt/chat这正好与前端 Vite 配置中的rewrite:pathpath.replace(/api/,/)保持一致。Nginx 官方文档也明确说明proxy_pass如果带 URI则匹配到的 location 部分会被proxy_pass指定的 URI 替换。([Nginx][2])3.4 为什么使用127.0.0.1:9012而不是118.31.19.221:9012这是另一个非常容易混淆的地方。服务器公网 IP118.31.19.221表示外部网络访问这台服务器的地址。而127.0.0.1表示当前服务器自身也就是 localhost。因此118.31.19.221:9012可以理解为外部 → 服务器 → 9012而127.0.0.1:9012表示Nginx → 本机 → 9012如果 Nginx 和 Java 服务都部署在同一台服务器那么proxy_pass http://127.0.0.1:9012/;是合理的。另外本次ss -lntp显示*:9012说明 Java 服务监听所有网卡因此本机通过127.0.0.1:9012也可以访问。四、最终 Nginx 配置解决/api/gpt/chat4044.1 推荐的完整nginx.conf当前服务器实际运行的 Nginx 是/usr/local/nginx/sbin/nginx实际配置文件/usr/local/nginx/conf/nginx.conf最终配置建议如下# Nginx 主配置 worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; # # 主站前端 后端 API # server { listen 80; server_name itsgpt.net.cn; # Vue 前端页面 location / { root html/dmx; index index.html index.htm; try_files $uri $uri/ /index.html; } # 禁止公网访问 Druid location ^~ /api/druid/ { deny all; return 404; } # 后端 API/api/* → 9012 后端服务 location ^~ /api/ { proxy_pass http://127.0.0.1:9012/; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $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 Authorization $http_authorization; # 大模型流式响应 proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; # 接口超时时间 proxy_connect_timeout 60s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # Njust API 服务 location ^~ /njust-api/ { proxy_pass http://127.0.0.1:18082/; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header Authorization $http_authorization; proxy_buffering off; proxy_read_timeout 300s; } # Njust RAG 服务 location ^~ /njust-rag/ { proxy_pass http://127.0.0.1:18084/; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; proxy_set_header Authorization $http_authorization; proxy_buffering off; proxy_cache off; chunked_transfer_encoding on; proxy_connect_timeout 60s; proxy_send_timeout 300s; proxy_read_timeout 300s; } # 错误页面 error_page 500 502 503 504 /50x.html; location /50x.html { root html; } } # # 8080 独立前端网站 # server { listen 8080; server_name itsgpt.net.cn; # 8080 网站前端 location / { root html/website; index index.html index.htm; try_files $uri $uri/ /index.html; } # 错误页面 error_page 500 502 503 504 /50x.html; location /50x.html { root html; } } # DomainMaster 独立入口 include conf.d/*.conf; }4.2 修改 Nginx 后正确的操作顺序不要修改完配置直接重启。第一步检查配置/usr/local/nginx/sbin/nginx-t-c/usr/local/nginx/conf/nginx.conf正常情况下应该看到syntax is ok test is successful第二步重新加载/usr/local/nginx/sbin/nginx-sreload第三步查看 Nginx 状态systemctl status nginx如果确实需要完全停止systemctl stop nginx启动systemctl start nginx完全重启systemctl restart nginx生产环境更推荐nginx-t↓ nginx-sreload因为只是修改配置时通常没必要先停止整个 Nginx。五、完整排查流程以后遇到 404 可以直接照着查5.1 第一步先判断是开发环境还是生产环境看到localhost:1002/api/xxx首先想到这是 Vite Proxy。看到itsgpt.net.cn/api/xxx首先想到这是 Nginx。两者不能混为一谈。5.2 第二步确认前端真正请求的 URI打开浏览器F12 → Network → 找到接口重点查看Request URL Request Method Status Code例如POST http://itsgpt.net.cn/api/gpt/chat 404记录真实 URI/api/gpt/chat5.3 第三步检查服务器到底有什么端口执行ss-lntp或者ss-lntp|grepLISTEN重点关注端口 PID 进程例如*:9012 → java 0.0.0.0:8081 → python 0.0.0.0:8083 → python 0.0.0.0:80 → nginx 0.0.0.0:8080 → nginx如果 Nginx 配的是8090但是ss-lntp|grep8090没有任何结果那么基本可以确定Nginx 正在转发到一个没有服务监听的端口。5.4 第四步直接绕过 Nginx 测试后端例如后端是 9012curl-ihttp://127.0.0.1:9012/针对具体接口curl-i-XPOST http://127.0.0.1:9012/gpt/chat如果需要 JSONcurl-i-XPOST http://127.0.0.1:9012/gpt/chat\-HContent-Type: application/json\-d{}这里要注意后端返回 400、422、参数错误不一定代表后端服务没启动。只要不是Connection refused就说明端口层面至少已经能够建立连接。5.5 第五步确认 Nginx 使用的到底是哪一个配置文件这次服务器执行ps-ef|grepnginx|grep-vgrep得到nginx: master process /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf这条信息非常重要。它直接告诉我们Nginx 程序 /usr/local/nginx/sbin/nginx 配置文件 /usr/local/nginx/conf/nginx.conf因此不能想当然地去修改/etc/nginx/nginx.conf或者其他目录里的配置。另外nginx -c可以指定 Nginx 启动时使用的配置文件官方文档也说明了-c参数用于指定配置文件路径。([Nginx][3])5.6 第六步检查 Nginx 当前加载的配置如果服务器可以直接执行 Nginx/usr/local/nginx/sbin/nginx-T-c/usr/local/nginx/conf/nginx.conf然后查看/usr/local/nginx/sbin/nginx-T-c/usr/local/nginx/conf/nginx.conf|grep-nserver_name也可以搜索/usr/local/nginx/sbin/nginx-T-c/usr/local/nginx/conf/nginx.conf|grep-n9012或者/usr/local/nginx/sbin/nginx-T-c/usr/local/nginx/conf/nginx.conf|grep-n8090如果发现实际加载配置里还存在proxy_pass http://127.0.0.1:8090;就说明旧配置仍然存在。六、这次问题的最终结论6.1 为什么本地正常因为本地使用Vite Dev Server ↓ server.proxy ↓ 9012也就是localhost:1002/api/gpt/chat ↓ Vite Proxy ↓ 192.168.110.90:9012/gpt/chat6.2 为什么服务器 404因为生产环境不再使用 Vite Proxy而是浏览器 ↓ itsgpt.net.cn ↓ Nginx ↓ 127.0.0.1:8090但是8090已经没有服务监听。与此同时真正的 Java 后端已经运行在118.31.19.221:9012因此产生了本地 → 9012 → 正常 服务器 → 8090 → 异常6.3 为什么之前明明配置了 9012却还是不走 9012因为存在location /api/gpt/chat这是精确匹配。而location ^~ /api/只是前缀匹配。对于/api/gpt/chatNginx 会优先使用精确匹配的location /api/gpt/chat所以之前的 9012 配置实际上没有接管这个接口。Nginx 官方文档对精确匹配和前缀 location 的选择规则有明确说明。([Nginx][1])七、注意事项以后排查类似问题重点看这几个地方7.1 不要把 Vite Proxy 当成生产代理server.proxy只负责开发环境。打包后Vite Proxy ❌ Nginx ✅7.2 修改后端端口时必须同步检查 Nginx例如后端从8090改成9012至少检查vite.config.ts Nginx nginx.conf 其他 conf.d/*.conf Docker 配置 systemd 服务不要只修改其中一个地方。7.3 遇到 404 不要马上改前端按照下面顺序排查效率最高① 浏览器 Request URL ↓ ② Vite Proxy / Nginx ↓ ③ Nginx location ↓ ④ proxy_pass ↓ ⑤ 后端端口 ↓ ⑥ curl 直接测试后端 ↓ ⑦ 查看后端日志尤其是ss-lntp是排查服务器服务端口非常实用的一条命令。7.4127.0.0.1不等于公网 IP记住118.31.19.221 → 服务器公网 IP 127.0.0.1 → 当前服务器本机 localhost → 当前服务器本机名称因此proxy_pass http://127.0.0.1:9012/;并不是配置错了。它表示Nginx 在当前服务器上直接访问当前服务器的 9012 端口。7.5 修改 Nginx 配置一定先测试推荐固定形成习惯/usr/local/nginx/sbin/nginx-t-c/usr/local/nginx/conf/nginx.conf确认syntax is ok test is successful再/usr/local/nginx/sbin/nginx-sreload不要养成“改完配置直接 restart”的习惯。八、最终形成一张排错速查表现象优先检查本地localhost:1002正常Vite Proxy服务器域名 404NginxlocationConnection refused后端端口没有服务Nginx 转发到旧端口检查proxy_pass配了/api/却没生效检查是否存在location xxx后端端口不知道是多少ss -lntp不知道端口对应哪个程序ps -fp PID不知道 Nginx 用哪个配置ps -efgrep nginx修改 Nginx 后不生效检查实际配置文件并 reload大模型流式响应异常proxy_buffering offVue 刷新页面 404try_files $uri $uri/ /index.htmlNginx 配置改完担心出错nginx -t九、本文结语这次问题表面上只是一个/api/gpt/chat → 404但真正原因并不在前端接口代码而是开发环境和生产环境的代理链路不同。最终完整链路应该明确为开发环境 浏览器 ↓ localhost:1002 ↓ Vite Proxy ↓ 9012 ↓ Java 后端 生产环境 浏览器 ↓ itsgpt.net.cn ↓ Nginx :80 ↓ /api/* ↓ 127.0.0.1:9012 ↓ Java 后端以后再遇到“本地接口正常、部署后接口 404”的问题不要第一时间修改前端代码先把这条链路逐层拆开。浏览器请求 → Vite/Nginx → location → proxy_pass → 端口 → 后端进程只要一层一层验证很快就能定位到问题。本文最值得记住的一句话本地能通只能证明 Vite Proxy 能通生产能不能通要看 Nginx 实际加载的配置、location 匹配结果以及后端真实监听端口。十、更多操作更多 Server 实战内容请看Server 个人专栏本文属于 Server 企业级实战系列持续更新 服务端开发、接口开发、数据库实战、服务器部署、性能调优、高并发解决方案等干货欢迎关注我的 CSDN 专栏Server如果本文对你有帮助欢迎点赞、收藏、评论你的支持是我持续输出实战干货的动力