把网站从主域名搬到二级目录,看起来只是把文件挪个位置,实际上是三条链路同时换地址:请求路径、资源引用、搜索索引。我在自己的一个工具站上做过一次迁移,前后折腾了将近两天,踩到的坑几乎都不是「文件放错地方」,而是「某个地方还记着旧地址」。这篇文章把整个过程拆成五个阶段,写完你可以照着做。
一、先想清楚为什么要二级目录
二级目录指的是一批独立功能放在同一个域名下的子路径里,比如 /tools/、/docs/、/blog/。常见的三种动因:
- 把散落的小站合并回主域。以前一个工具一个二级域名,每个域名都要单独做备案、单独买证书、单独被搜索引擎当新站评估,权重永远是散的。
- 给不同内容线分家。工具、文档、博客混在根目录,URL 越来越难辨认,也没有办法给某一条线单独配置缓存和访问控制。
- 为后续拆分留退路。先合成一个域,将来真要拆出去的时候,目录路径比根目录路径更容易做 301 映射。
反过来说,如果只是想整理文件目录,不要去动 URL。URL 是站点的身份,改一次就要付一次索引重建的代价。判断标准很简单:改动能不能带来实际收益,比如少维护几个域名、少买几张证书、把分散的权重收回来。收益说不清楚的,就算了。
二、URL 方案先定死,再动文件
迁移时最容易返工的一步是「边搬边想 URL」。文件搬完了才发现路径规则不合适,想再改一次,就得做第二轮跳转,而两轮 301 叠在一起,搜索引擎抓取会出问题。所以先把 URL 表写出来,写成文本,逐条确认。
我的规则是这样的:
旧:https://tool.example.com/article/nginx-gzip.html
新:https://www.example.com/tools/nginx-gzip.html
旧:https://tool.example.com/
新:https://www.example.com/tools/
旧:https://tool.example.com/search?q=xxx
新:https://www.example.com/tools/search?q=xxx三条硬规矩:
- 只加前缀,不改后缀。能保留原来的文件名就保留,这样映射规则是一条通配就能覆盖的,不需要几百行一一对应。
- 结尾斜杠统一。目录页一律带斜杠、文件页一律不带,两者混用会让同一个页面出现两个可访问版本,等于自己造重复内容。
- 查询参数原样保留。跳转规则里必须带
$args,否则所有带参数的页面(搜索页、分页、跟踪链接)都会被打回无参版本。
三、文件搬迁:路径、权限、大小写
文件层的坑集中在三处。
第一是软链接残留。把整个站点目录搬到新位置后,原目录里的相对软链接会指向错误的地方。用下面这条命令在搬迁后扫一遍:
find /home/wwwroot/www.example.com/tools -type l -exec ls -l {} \;
find /home/wwwroot/www.example.com/tools -xtype l第二条命令专找已经断掉的链接,返回非空就说明有文件搬了但链接没跟上。
第二是属主和权限。用 mv 在同一块盘上搬是原子的,属主不变;跨盘搬、或者用 cp -r 再删,很容易变成 root:root,然后 PHP 写缓存、写上传目录全部失败,页面显示 500 但日志只有一句「Permission denied」。搬迁后统一收一次:
chown -R www:www /home/wwwroot/www.example.com/tools
find /home/wwwroot/www.example.com/tools -type d -exec chmod 755 {} \;
find /home/wwwroot/www.example.com/tools -type f -exec chmod 644 {} \;注意 chmod 那两条不能写成 chmod -R 755,那会把所有文件都变成可执行,虽然不影响访问,但会让后续的安全扫描满屏告警。
第三是大小写。Linux 文件系统区分大小写,很多人是在 Windows 或 macOS 上开发的,本地引用写成 Logo.PNG、服务器上文件叫 logo.png,本地一切正常,上线后图片全挂。搬迁时顺手做一次全量检查:
grep -roE '(src|href)="[^"]+"' /path/to/tools --include=*.html \
| sed -E 's/.*="([^"]+)"/\1/' | grep -v '^http' | sort -u \
| while read f; do p="/path/to/tools/${f#/}"; [ -e "$p" ] || echo "MISSING $f"; done输出为空才继续下一步。
四、配置:三个必须同步改的地方
文件到位了,配置往往还停在旧地址上。常见的三处:
Nginx 的 root 与 location。如果新目录还是要用 PHP 处理,location ~ \.php$ 那一段的 fastcgi_param SCRIPT_FILENAME 必须指到新路径的绝对地址,漏改的表现是下载 php 文件或者直接 404。整段建议抽成 snippet 共用,避免同一个配置在多个站点里各写一份。
应用内的 base_url / site_url。Typecho、WordPress、Laravel 这类框架都在配置文件或数据库里存了站点根地址。搬到二级目录后如果这个值还是 https://www.example.com,那么模板生成的链接会全部指回根目录,页面能打开,但所有内链都跳到 404。这里要改的是「带目录的根」,比如 https://www.example.com/tools。
反向代理的 proxy_pass。如果二级目录是反代到本地某个端口的应用,路径前缀要不要保留取决于应用自身:proxy_pass http://127.0.0.1:8080/; 带斜杠会剥掉前缀,不带斜杠会保留。剥掉之后应用以为自己在根目录,静态资源路径就会错。这条规则改完必须用 curl -I 看静态资源的返回码,不能只看首页。
五、301 跳转:通配规则与常见错法
旧域名必须逐个 URL 跳到新地址,而不是整站跳首页。整站跳首页在搜索引擎看来是「旧页面全部失效」,已经积累的排名清零。
最简单的做法是在旧域名的 server 块里加一条规则,用 rewrite 保留路径与参数:
server {
listen 443 ssl;
server_name tool.example.com;
location / {
return 301 https://www.example.com/tools$request_uri;
}
}注意 $request_uri 本身就带查询参数,所以这里不需要再拼 ?$args,写了反而会变成 ??q=xxx。而在 rewrite 指令里必须带 ?$args,因为 rewrite 替换后默认丢掉参数。return 和 rewrite 的这个差别,是迁移时最容易搞混的一条。
另外两件必须做的事:跳转规则放在 location / 里但要在 /.well-known/acme-challenge/ 之前单独放过,否则旧域名的证书续期会被跳走导致失败;跳转上线后抽十条代表性 URL 手工验一遍,包括首页、文章页、带参数的搜索页、上传目录里的文件,全部 301 到正确的目标才算通过。
六、资源引用:相对路径与绝对路径的取舍
搬到子目录后,站内资源引用一定要用绝对路径或者配置化的 base 标签,不要依赖相对路径。原因很直接:文章页在 /tools/a/b.html,相对路径 ../img/x.png 解析出来和原来在根目录时不一样,深层页面和浅层页面的资源会同时挂掉一部分。
统一改成以斜杠开头的绝对路径是最省心的:/tools/img/x.png。这样无论页面在哪个深度,解析结果都一样。代价是将来再搬家要全站替换一次——所以我倾向于在模板变量里存一份前缀,页面里写 {$prefix}/img/x.png,将来只改一处。
<base href> 标签能做到同样的事,但它会同时改变页面内所有相对链接和锚点的解析基准,包括表单提交地址,一旦基准写错,表现会非常诡异,不建议在已有站点的迁移中引入。
七、验证清单:八个必须返回 200 的地址
迁移完成后,按下面这条命令一次性拉清单,任何一行不是 200 就不能宣布完成:
for u in / /tools/ /tools/index.html /tools/sitemap.xml \
/tools/robots.txt /tools/feed/ /tools/img/logo.png \
/tools/logo-page.html; do
printf '%s ' "$(curl -s -o /dev/null -w '%{http_code}' \
"https://www.example.com$u")"
echo "$u"
done再加两条行为验证:随便挑一个旧的文章地址,用 curl -sI 确认是 301 且 Location 落在正确的新地址上,不能落在首页;然后在手机浏览器里打开三个页面,看一眼图片和样式是否完整,这一步经常能发现 CSS 里 url() 还指向旧目录的问题。
八、收录:sitemap、内链与旧链接回收
技术上跳转做好之后,搜索引擎还需要时间接受新地址,这个过程可以加速:
- sitemap 全部换成新 URL,旧 sitemap 保留一段时间(至少四周),里面全是 301 地址也没关系,它能让爬虫更快发现跳转关系。
- robots.txt 的 Sitemap 行改成新的地址,这个文件在某些搜索引擎里的优先级比 sitemap 索引更高。
- 内链全部换成新地址。站内链接如果还指向旧地址,等于每次点击都要多一次 301 往返,既慢又浪费抓取预算。用
grep -r 'tool.example.com' | wc -l数一下,改到 0 为止。 - 外链尽量谈。能联系到的合作方就让他们改直链,不能联系的靠 301 兜着。
还有一个容易忽略的:站内搜索的结果、标签页、分页链接里如果写死了旧域名,这些页面会被搜索引擎大量发现并重复抓取,属于典型的内耗。批量替换一次,比事后一个个修便宜得多。
九、小结:先定表,再搬文件,最后改配置
二级目录迁移的顺序不能乱:先写 URL 映射表,再搬文件并修权限和软链接,然后改配置和资源引用,接着上 301,最后处理 sitemap 和内链。每一步都有一个可以验证的产出物——映射表、find -xtype l 为空、nginx -t 通过、十条 URL 全部 301、sitemap 里旧域名计数为 0。把这几项当成闸门,任何一项没过就不进入下一步,整个迁移可以控制在两小时以内,而且不会出现「上线一半发现文件找不到」的场面。
