0. 问题背景
最近在给课题组部署服务时,遇到一个非常诡异的需求:将我的 Halo 博客(运行在 8090 端口)挂载到主站域名的子目录 /ai/ 下(例如 sedst.org/ai/),而不是使用子域名。
看似简单的 proxy_pass,却让我陷入了长达数小时的 Debug 泥潭:
现象:手动启动 Java 程序时,访问
/ai/一切正常;但一旦切换成 Systemd 服务自动启动,页面就无限 404 或者白屏。环境:Ubuntu + Nginx + Halo 2.x (Spring WebFlux) + Vue 前端。
1. 案发现场:诡异的“拦截失效”
起初,我以为是 Nginx 配置写得不对,导致请求没有转发给后端。这是我最初的配置:
Nginx
location ^~ /ai/ {
proxy_pass http://127.0.0.1:8090/;
sub_filter 'src="/' 'src="/ai/'; # 尝试暴力替换路径
sub_filter_once off;
}
2. Nginx 的优先级陷阱与 Vue 干扰
即使解决了后端路径问题,页面依然偶发 404。原来是因为我的主站是 Vue 项目,Nginx 配置里有这两段“老赖”:
静态资源正则:
location ~ .*\.(js|css)?$ { ... }Vue 伪静态:
try_files $uri $uri/ /index.html;
冲突逻辑: 即使我请求的是 /ai/style.css,如果配置不当,Nginx 的正则匹配优先级(~)可能会抢在普通前缀匹配之前生效。导致 Nginx 不去转发给 Halo,而是试图在本地磁盘找这个文件,结果当然是找不到。
这也是为什么这次 Debug 中感觉到“被 Vue 拦截了”——其实是被适配 Vue 的 Nginx 规则拦截了。
3. 终极解决方案
为了彻底解决问题,我重构了 Nginx 配置,利用 ^~ 提权,并配合 sub_filter 做双重保险。
核心配置代码:
Nginx
# 使用 ^~ 提高优先级,确保 /ai/ 开头的请求绝对优先转发,不被后面的正则或伪静态抢走
location ^~ /ai/ {
proxy_pass http://127.0.0.1:8090/;
# 基础 Header
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 关键:Websocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 关键:禁止后端压缩,确保 sub_filter 能工作
proxy_set_header Accept-Encoding "";
# 双重保险:强制替换 HTML 中的绝对路径引用
sub_filter 'src="/' 'src="/ai/';
sub_filter 'href="/' 'href="/ai/';
sub_filter_once off;
}
4. 总结与反思
这次 Debug 让我深刻理解了 Linux 运维的三大定律:
环境一致性:
systemctl启动和java -jar手动启动是两码事,WorkingDirectory 是极其容易被忽略的变量。Nginx 优先级:
^~>~(正则) > 普通前缀。在混合部署(Vue + 后端代理)时,一定要用^~保护好你的代理路由。路径一致性:子目录部署最怕前后端路径认知不一致。后端必须配置
base-path,前端资源引用最好是相对路径,或者由 Nginx 进行sub_filter兜底。