WordPress .htaccess 重定向规则:把 301 写对
换掉旧网址又不丢排名:该用哪条 WordPress .htaccess 重定向指令、规则必须写在哪里,以及怎样测试才不会把自己关在后台外面。
发布于
想换掉一个网址,又想让旧网址的排名继续留住?你需要在 .htaccess 里写一条 301:放在 WordPress 不会抹掉的位置,用对指令,并且指向 WordPress 实际输出的那个网址。这三点错任何一点,等着你的就是重定向链、死循环,或者一个把你挡在 wp-admin 之外的 500。
按判断、位置、测试的顺序来看。
用哪条指令:Redirect、RedirectMatch 还是 RewriteRule
牵涉到两个 Apache 模块,它们并不能互相替代。
Redirect(mod_alias) —— 一个已知路径对应一个目标。能用就够简单:
Redirect 301 /old-page/ https://example.com/new-page/
有两个行为要知道。它按路径前缀匹配,不是精确字符串匹配,所以 /old-page/ 也会捕获 /old-page/anything/,并把 /anything/ 追加到目标地址后面。另外它会自动把查询字符串带过去。
RedirectMatch(mod_alias) —— 一条正则覆盖大量网址。整块栏目搬家就靠它:
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
捕获组 (.*) 在目标地址里变成 $1,于是 /blog/hello-world/ 会落到 /articles/hello-world/。注意开头的斜杠:mod_alias 的模式匹配的是完整的网址路径。
RewriteRule(mod_rewrite) —— 当重定向取决于某个条件时必须用它:主机名、协议、查询字符串、用户代理。mod_alias 没有任何手段能判断这些。
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
在目录级的 .htaccess 里,模式开头的斜杠会被去掉 —— 这就是这里写 ^(.*)$、而 mod_alias 例子里写 ^/blog/ 的原因。把这一点搞混,是复制来的规则悄无声息毫无反应的头号原因。
默认选 mod_alias。只有确实需要判断条件时才去动 RewriteRule。正则越少,出错的路子也越少。
规则必须放在哪里
自定义重定向要放在 # BEGIN WordPress 这一行上方,不是放在里面。
# --- custom redirects ---
Redirect 301 /old-page/ https://example.com/new-page/
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
两个彼此独立的理由:
- WordPress 会重写自己那一段。 保存「设置 → 固定链接」会调用
flush_rewrite_rules(),把两个标记之间的内容全部丢弃再重新生成。你放在里面的规则,会在下一次有人碰那个页面时消失 —— 可能是几个月以后,而且不会有人把这两件事联系起来。 - 顺序决定谁说了算。 WordPress 那一段以一条兜底规则收尾,把所有既不是文件也不是目录的请求送往
index.php。写在它后面的RewriteRule永远不会执行。
有一处复杂的地方值得如实说明:mod_alias 和 mod_rewrite 实际上并不按文件里的先后顺序执行。Apache 在 URL 转换阶段处理 mod_alias,而目录级的 mod_rewrite 要到后面的 fixup 阶段才处理 —— 所以一条 Redirect 可以压过文件中写在它上方的 RewriteRule。如果你发现自己在重叠的路径上两个都要用,那就为这条路径选定一个模块,然后守在这个模块里。为 mod_alias 和 mod_rewrite 打架而调试一小时,不值得。
结尾斜杠
WordPress 自带的规范化重定向会给绝大多数固定链接补上结尾斜杠。所以这样写:
Redirect 301 /old-page/ https://example.com/new-page
会产生两跳 —— 先是你的 301 跳到没有斜杠的网址,再是 WordPress 自己的 301 把斜杠补上。重定向链仍然传递排名信号,但会浪费抓取预算,并且给每位访客多加一次往返。
先在浏览器里打开目标页面,把地址栏里最终稳定下来的网址原样复制出来,用这个地址。如果你的固定链接结构以 .html 结尾或者不带结尾斜杠,那就照那样匹配。这里没有放之四海皆准的答案,只有一条:匹配 WordPress 实际输出的东西。
测试时别把自己锁在外面
.htaccess 每个请求都会被读取一次。一个语法错误会让整站包括 wp-admin 都返回 500,所以恢复的路子必须在你用得着之前就准备好。
先备份文件。 通过 SSH:
cp /path/to/wordpress/.htaccess /path/to/wordpress/.htaccess.bak
站点一旦 500,把备份改名覆盖回去,站点立刻就回来了。另开一个窗口,让 SFTP 会话一直连着 —— 站点已经挂掉时才从头开始登录,恐慌就是从那一刻开始的。另外要注意,apachectl configtest 不会解析 .htaccess,所以你的站点已经死了,它还会报告配置一切正常。
用 curl 测,别用浏览器。 浏览器会把 301 响应缓存得极死,还会心安理得地把昨天的结果拿给你看:
curl -sI https://example.com/old-page/ | head -n 5
看两样东西:状态行应该是 301,Location: 响应头应该正是那个最终网址。想看清整条链,就跟着它走:
curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'
出现不止一行 HTTP/1.1 301,说明你造了一条链。超过五行左右,说明你造了个死循环,curl 会停下来告诉你。
还没把握的时候先用 302。 302 不会被同样地缓存,所以出错几秒钟就能挽回。等 curl 显示的目标地址正是你想要的,再切成 301。这一步不花任何成本,救下的站点比本文里任何一个习惯都多。
如果一条规则看起来完全没有起作用,先确认 Apache 到底有没有在读这个文件。目录上设置了 AllowOverride None,Apache 就会彻底忽略 .htaccess;而 nginx 根本就不看它。在钻研正则之前,先用 curl -I 看一眼 Server: 响应头。要按你的 Apache 版本和安装路径生成语法正确的规则段,可以用 WordPress .htaccess 生成器。
什么时候根本不该用 .htaccess
如果只是少量需要编辑人员自己维护的重定向,那么把规则存进数据库的重定向插件才是更合适的工具:它能扛过主机迁移,也不需要 SSH。代价也是实打实的:得启动 PHP 才能返回这个重定向,比 Apache 直接应答要慢。
.htaccess 用于结构性的、永久性的搬迁 —— 换域名、栏目改名、强制 https 或 www。一次性的编辑类重定向交给插件。两者并用也没问题,前提是你清楚哪个网址归哪一层管,因为在两个地方定义的同一条重定向,就是一个正等着某个糟糕下午发作的 bug。
FAQ
常见问题
自定义重定向应该写在 WordPress 的 .htaccess 文件的哪个位置?
写在 # BEGIN WordPress 这一行的上方,绝不能写在两个标记之间。只要有人保存一次固定链接设置页面,WordPress 就会重新生成标记内的全部内容,写在里面的规则会被悄无声息地删掉。位置对执行顺序同样关键:WordPress 那段以一条兜底规则收尾,把未命中的请求全部送往 index.php。
做 301 应该用 Redirect、RedirectMatch 还是 RewriteRule?
单个已知路径用 Redirect,一条正则覆盖大量网址用 RedirectMatch,重定向要依赖主机名、协议或查询字符串等条件时用 RewriteRule。Redirect 和 RedirectMatch 来自 mod_alias,写法更简单。RewriteRule 来自 mod_rewrite,是唯一能判断条件的指令。
我的 .htaccess 重定向为什么会造成重定向死循环?
通常是目标网址本身仍然命中把访客送过去的那条规则,于是规则一遍遍触发。另一个常见原因是在负载均衡器或 CDN 后面强制 https:访客明明已经走 https,服务器看到的每个请求却仍是明文 http。这种情况应改为判断 X-Forwarded-Proto 请求头。
WordPress 的重定向要不要在意结尾斜杠?
要。WordPress 的规范化重定向会给绝大多数固定链接补上结尾斜杠,所以把 301 指向一个没有斜杠的网址,本来一跳能完成的事会变成两跳。重定向链依然会传递排名信号,但会浪费抓取预算,也让访客等得更久。请精确匹配 WordPress 实际输出的最终网址,斜杠也算在内。
怎样测试 .htaccess 重定向才不会把站点搞坏?
在相信浏览器之前,先用带只取响应头参数的 curl 请求旧网址,读状态码和 Location 响应头。浏览器会把 301 响应缓存得很死,给你看的往往是旧结果。另外一定要先留一份可用文件的副本,因为 .htaccess 里的语法错误会让包括 wp-admin 在内的所有页面返回 500。
我加了一条重定向以后整站返回 500 错误,这是为什么?
.htaccess 里只要有一条指令写错,该目录下的所有网址都会一起停摆,后台也不例外。常见原因是 IfModule 没有闭合、漏写 RewriteEngine On 这一行,或者把 Apache 2.2 和 2.4 的访问控制语法混在同一个文件里。删掉你最后加的那一段再刷新,就能确认。