前言

一台 Web 服务器长期运行后,常会逐渐积累多个独立应用。最初部署时,为了省事,很多应用直接依赖站点根目录发布:磁盘目录叫 tool-alpha,公网地址也自然变成 /tool-alpha/

这种方式能够工作,但公网 URL 与磁盘目录形成了不必要的一一绑定。项目目录一旦带有内部命名、历史命名或技术实现含义,公网地址也会跟着暴露;以后想调整 URL,又容易误以为必须移动项目目录。

更稳妥的做法是:

保持应用目录和程序结构不动,只在 Web 服务器配置层重新定义公网入口。

本文记录一次经过审计、分层验证和安全切换的实践。示例中的域名、主机名、应用名、目录、端口和配置文件名均为虚构内容。


一、改造前的典型结构

示例环境有两层 Web 服务:

公网客户端
  ↓ HTTPS
edge-gateway(Nginx)
  ↓ HTTP 反向代理
web-node(Apache)
  ↓
多个静态、PHP 和本地后端应用

网关层使用通用代理规则,将普通路径原样转发给后端 Apache:

location / {
   proxy_pass http://10.0.0.20;
}

后端 Apache 的站点根目录是:

/srv/web/

多个应用原先直接按目录名对外发布:

/srv/web/app-alpha/   → /app-alpha/
/srv/web/app-beta/   → /app-beta/
/srv/web/app-gamma/   → /app-gamma/

目标是把它们改为更稳定、简洁的公网入口,例如:

/app-alpha/   → /tools-a/
/app-beta/   → /tools-b/
/app-gamma/   → /lookup/

同时,磁盘目录完全不移动。


二、为什么要分离 URL 与真实目录

这次改造不是为了提高运行性能。Apache 在启动或 reload 时解析配置,正常请求不会因为多几个小型配置文件就明显变慢。一个大配置文件和多个小配置文件,在这种规模下几乎没有可感知的性能差异。

真正的收益是结构治理:

  1. 公网命名不再受磁盘目录限制 项目目录可以继续沿用技术名称、仓库名称或历史名称,公网入口则面向使用者命名。
  2. 以后更容易调整入口 更换 URL 不必移动代码、改所有者或重新设计目录结构。
  3. 降低误操作范围 每个应用独立配置,一个应用出错时,不必动其他应用。
  4. 安全边界更清楚 静态应用、PHP 应用和本地反向代理应用可以分别保留自己的访问控制。
  5. 回滚更直接 新入口失败时,只需停用对应配置,原路径仍可继续工作。

三、配置组织:一应用一文件

最终采用“一应用一个 Apache 配置文件”的方式:

/etc/apache2/conf-available/tool-a.conf
/etc/apache2/conf-available/tool-b.conf
/etc/apache2/conf-available/tool-c.conf
/etc/apache2/conf-available/tool-d.conf
/etc/apache2/conf-available/data-site.conf
/etc/apache2/conf-available/backend-app.conf

没有把所有 Alias 集中到一个总文件,也没有合并系统自带的 security.conf、PHP-FPM 配置、字符集配置等文件。

这种方式文件数量稍多,但优势明显:

  • 可以单独 a2enconfa2disconf
  • 语法错误和访问异常更容易定位;
  • 不同应用的安全规则不会混在一起;
  • 以后移除应用时,不需要编辑一个越来越大的总文件。

四、静态应用的 Alias 配置

以一个静态工具为例,真实目录保持为:

/srv/web/app-alpha/

新公网入口为:

/tools-a/

独立配置可以写成:

# Static Tool A
RedirectMatch 301 ^/tools-a$ /tools-a/

Alias "/tools-a/" "/srv/web/app-alpha/"

<Directory "/srv/web/app-alpha/">
  Options -Indexes
  AllowOverride None
  Require all granted
</Directory>

这里有几个关键点:

  • RedirectMatch 301 统一补上结尾斜杠;
  • Alias 只改变 URL 到目录的映射;
  • Options -Indexes 禁止目录索引;
  • 不修改目录所有者和权限;
  • 不给 Web 进程增加写权限。

对于纯前端应用,还要检查:

  • CSS、JavaScript、图片和字体是否使用相对路径;
  • manifest 的 start_urlscope
  • Service Worker 注册路径;
  • Worker、WASM 或数据包路径;
  • 是否硬编码了旧 URL 前缀。

若应用一直使用相对路径,通常无需改动任何程序文件。


五、PHP 应用不能只看首页

某些 PHP 应用虽然也可以通过 Alias 改入口,但它们通常还有独立的访问控制,例如禁止访问:

app/
data/
scripts/
reports/
docs/

此时不应为了“配置整齐”重写原有安全规则。更稳妥的做法是:

  • 新路由文件只负责新的 Alias
  • 原有访问控制文件继续负责敏感目录;
  • 上线后逐个测试敏感路径仍返回 403404
  • 同时确认 PHP 被执行,而不是以源码形式下载。

功能验证也不能停留在首页 200,至少应覆盖:

  • 搜索页;
  • 详情页;
  • 表单提交;
  • 导出接口;
  • 静态资源;
  • 内部目录拦截。

六、本地后端应用需要同步应用根路径

另一类应用不是由 Apache直接读取目录,而是:

Apache ProxyPass
  ↓
127.0.0.1:9000 上的本地应用服务

这类应用不能只修改 Apache。通常还要同步:

  • APPLICATION_ROOT
  • 公开 URL;
  • Session Cookie Path;
  • 页面中的应用根路径;
  • API、下载和静态资源地址;
  • 健康检查路径。

安全做法是以旧代理配置为模板,只替换 URL 前缀,其他代理参数、端口和请求头设置保持原样。生成新配置后使用 diff 检查,确认变化仅限旧前缀到新前缀。

在重载应用服务前,还应确认没有正在执行的任务,避免中断后台作业。


七、最安全的切换顺序

整个迁移不应先关闭旧路径。推荐顺序如下:

创建新入口
→ Apache configtest
→ 平滑 reload
→ 验证新入口和全部资源
→ 更新正式导航
→ 备份已生效配置
→ 关闭旧路径
→ 再次 configtest 和 reload
→ 完整回归测试

旧路径最后通过明确的 404 规则关闭:

RedirectMatch 404 ^/app-alpha(?:/.*)?$

这里不使用重定向到新地址,原因是此次目标是彻底停止旧入口,避免旧书签、旧资源地址和目录名继续长期存在。


八、备份也应按对象分类

配置备份和 Web 文件备份不宜混为一谈。

系统配置

Apache、systemd 等系统配置,适合使用专门的配置备份工具,在原配置目录中生成保留时间戳的副本。这样恢复方便,也不会把系统配置散落到其他归档目录。

Web 目录中的应用文件

若必须修改应用文件,应把备份移出对外开放的 Web 根目录,并按原绝对路径镜像保存,例如:

/srv/web/backend-app/app.py

/secure-backup/srv/web/backend-app/app.py

不应在公开目录旁边留下:

app.py.bak
app.py.old
app.py.20260101

否则备份文件可能被浏览器直接下载。


九、两次容易误判的问题

1. 检查到了错误主机或错误文件系统

自动化执行前必须设置硬门槛:

hostname
当前用户
目标 IP
findmnt
预期目录清单
预期配置清单

任意一项不符,立即停止。仅凭主机名相似或 SSH 命令“看起来成功”并不够。

2. 公网响应头显示 Nginx,不代表 Apache 没生效

在多层代理结构中,公网看到:

Server: nginx

只能证明最外层是 Nginx,不能证明请求没有进入后端 Apache。

正确诊断必须分层测试:

后端主机本机请求 Apache
网关主机直接请求后端
公网请求网关

只有这样才能判断问题究竟在 Alias、Apache 配置加载、网关 location,还是测试时序。


十、没有浏览器自动化时如何验证

浏览器点击仍然是最直观的验收方式,但服务器环境可能没有显示服务器或浏览器节点。此时可以组合使用:

  • HTTP 状态码和响应头检查;
  • 逐个请求页面引用的静态资源;
  • 检查 manifest 和 Service Worker;
  • 使用本地 JavaScript 运行时执行核心逻辑;
  • 使用无害查询验证 PHP 页面;
  • 检查敏感目录状态码;
  • 验证 Cookie Path、健康检查和应用根路径。

需要注意:摄像头调用、剪贴板、PWA 离线行为等浏览器能力,仍应在方便时补做人工体验验证。这不影响服务器路由已经成功切换的判断,但属于完整体验验收的一部分。


十一、最终结果

本次迁移完成了以下目标:

  • 多个应用获得新的、独立的公网入口;
  • 磁盘目录和应用目录完全未移动;
  • 每个应用拥有独立 Apache 配置;
  • 静态应用、PHP 应用和本地后端应用分别验证;
  • 旧入口在最终切换后统一返回 404;
  • 原有敏感目录保护继续生效;
  • 网关层无需修改;
  • Apache 仅执行语法检查和安全 reload;
  • 未通过修改权限来“解决”路由问题;
  • 未在公开目录中留下备份文件。

这类改造的核心并不是写几行 Alias,而是控制变更顺序:先建立新路径,证明它完整可用,再关闭旧路径。只要坚持分层诊断、分类备份和逐应用验证,URL 与真实目录的解耦可以在不中断现有服务的情况下安全完成。

Leave a Reply

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