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 在網址轉換階段處理 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。一次性的編輯類重新導向就交給外掛。兩者並用也沒問題,前提是你清楚哪個網址歸哪一層管,因為在兩個地方定義同一條重新導向,就是一個正在等待某個糟糕午後發作的錯誤。
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 的存取控制語法混在同一個檔案裡。把最後加入的那一段移除再重新載入,就能確認。