yanchang
yanchang
发布于 2026-02-04 / 11 阅读
0
0

Nginx 反代 Halo 404 之谜:当 Systemd 遇上 Vue History 模式

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 配置里有这两段“老赖”:

  1. 静态资源正则location ~ .*\.(js|css)?$ { ... }

  2. 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 运维的三大定律:

  1. 环境一致性systemctl 启动和 java -jar 手动启动是两码事,WorkingDirectory 是极其容易被忽略的变量。

  2. Nginx 优先级^~ > ~ (正则) > 普通前缀。在混合部署(Vue + 后端代理)时,一定要用 ^~ 保护好你的代理路由。

  3. 路径一致性:子目录部署最怕前后端路径认知不一致。后端必须配置 base-path,前端资源引用最好是相对路径,或者由 Nginx 进行 sub_filter 兜底。


评论