Quy tắc chuyển hướng .htaccess trong WordPress: làm 301 cho đúng
Đổi URL mà không mất thứ hạng: chọn đúng directive chuyển hướng .htaccess cho WordPress, đặt rule vào đúng chỗ, và test mà không tự khóa mình ra ngoài.
Đã đăng
Bạn đổi URL và muốn URL cũ giữ nguyên thứ hạng? Bạn cần một 301 trong .htaccess, đặt ở chỗ WordPress không xóa mất, viết bằng đúng directive, và trỏ tới đúng URL mà WordPress thực sự phục vụ. Sai bất kỳ điểm nào trong ba điểm đó là bạn có một chuỗi chuyển hướng, một vòng lặp, hoặc một lỗi 500 khóa bạn ra khỏi wp-admin.
Quyết định chọn directive, quy tắc đặt vị trí, rồi cách test — theo đúng thứ tự đó.
Chọn directive nào: Redirect, RedirectMatch hay RewriteRule
Có hai module Apache tham gia vào cuộc chơi này và chúng không thay thế được cho nhau.
Redirect (mod_alias) — một đường dẫn đã biết trỏ tới một đích. Đơn giản nhất mà vẫn chạy:
Redirect 301 /old-page/ https://example.com/new-page/
Có hai hành vi cần nắm. Nó khớp theo tiền tố đường dẫn, không phải khớp chuỗi chính xác, nên /old-page/ cũng bắt luôn /old-page/anything/ và nối /anything/ vào đích. Và nó tự động mang theo query string.
RedirectMatch (mod_alias) — một regex bao nhiều URL. Đây là cách bạn di dời cả một chuyên mục:
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
Nhóm bắt (.*) trở thành $1 ở phía đích, nên /blog/hello-world/ sẽ đáp xuống /articles/hello-world/. Chú ý dấu gạch chéo đầu: pattern của mod_alias khớp với toàn bộ đường dẫn URL.
RewriteRule (mod_rewrite) — bắt buộc khi việc chuyển hướng phụ thuộc vào một điều kiện: hostname, giao thức, query string, user agent. Không có gì trong mod_alias kiểm tra được những thứ đó.
RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]
Pattern trong một file .htaccess cấp thư mục bị cắt mất dấu gạch chéo đầu — đó là lý do ở đây viết ^(.*)$ còn ví dụ mod_alias phía trên viết ^/blog/. Nhầm lẫn chỗ này là nguyên nhân số một khiến một rule copy về dán vào lại lặng lẽ không làm gì cả.
Mặc định hãy dùng mod_alias. Chỉ với tới RewriteRule khi bạn thật sự cần một điều kiện. Ít regex hơn thì cũng ít chỗ để sai hơn.
Các rule phải nằm ở đâu
Chuyển hướng tùy chỉnh thuộc về phần phía trên dòng # BEGIN WordPress. Không phải bên trong nó.
# --- 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
Có hai lý do độc lập với nhau:
- WordPress ghi lại khối của chính nó. Lưu ở Settings → Permalinks sẽ gọi
flush_rewrite_rules(), hàm này bỏ đi mọi thứ nằm giữa các marker rồi tạo lại. Một rule bạn đặt bên trong sẽ biến mất vào lần kế tiếp có ai đó đụng vào màn hình đó — có thể là nhiều tháng sau, và không ai nối được hai sự kiện lại với nhau. - Thứ tự quyết định ai thắng. Khối WordPress kết thúc bằng một rule bắt tất cả, đẩy mọi request không phải file và không phải thư mục về
index.php. MộtRewriteRuleđặt sau nó sẽ không bao giờ chạy.
Một điểm phức tạp cần nói thẳng: mod_alias và mod_rewrite thực ra không chạy theo thứ tự trong file. Apache xử lý mod_alias ở giai đoạn dịch URL, còn mod_rewrite cấp thư mục thì muộn hơn, ở giai đoạn fixup — nên một dòng Redirect có thể thắng một RewriteRule nằm phía trên nó trong file. Nếu bạn thấy mình cần cả hai trên những đường dẫn chồng lấn nhau, hãy chọn một module cho đường dẫn đó và ở lì trong module ấy. Ngồi gỡ một cuộc đấu giữa mod_alias và mod_rewrite không đáng một tiếng đồng hồ.
Dấu gạch chéo cuối
Cơ chế canonical redirect của chính WordPress thêm dấu gạch chéo cuối vào hầu hết permalink. Nên đoạn này:
Redirect 301 /old-page/ https://example.com/new-page
tạo ra hai chặng — 301 của bạn tới URL không có dấu gạch chéo, rồi 301 của chính WordPress thêm dấu gạch chéo vào. Chuỗi chuyển hướng vẫn truyền tín hiệu xếp hạng, nhưng chúng lãng phí crawl budget và thêm một vòng đi về cho mỗi khách truy cập.
Hãy mở đích đến trên trình duyệt trước, copy chính xác URL khi nó đã ổn định trên thanh địa chỉ, rồi dùng URL đó. Nếu cấu trúc permalink của bạn kết thúc bằng .html hoặc không có dấu gạch chéo cuối, hãy khớp theo cấu trúc đó. Ở đây không có đáp án đúng cho mọi trường hợp, chỉ có nguyên tắc “khớp đúng thứ WordPress phục vụ”.
Test mà không tự khóa mình ra ngoài
.htaccess được đọc ở mọi request. Một lỗi cú pháp trả về 500 cho toàn site, kể cả wp-admin, nên đường lui phải có sẵn trước khi bạn cần đến nó.
Sao lưu file trước đã. Qua SSH:
cp /path/to/wordpress/.htaccess /path/to/wordpress/.htaccess.bak
Nếu site trả 500, đổi tên bản sao đè lên file hỏng là site trở lại ngay lập tức. Hãy giữ sẵn một phiên SFTP đang mở ở cửa sổ khác — đăng nhập lại từ đầu trong lúc site đang chết là lúc cơn hoảng loạn bắt đầu. Lưu ý rằng apachectl configtest không phân tích .htaccess, nên nó sẽ báo cấu hình khỏe mạnh trong khi site của bạn đã chết.
Test bằng curl, đừng test bằng trình duyệt. Trình duyệt cache phản hồi 301 rất dai và sẽ vui vẻ cho bạn xem kết quả của hôm qua:
curl -sI https://example.com/old-page/ | head -n 5
Hãy đọc hai thứ: dòng trạng thái phải ghi 301, và header Location: phải đúng là URL cuối cùng. Muốn xem cả chuỗi thì đi theo nó:
curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'
Nhiều hơn một dòng HTTP/1.1 301 nghĩa là bạn đã dựng ra một chuỗi. Nhiều hơn khoảng năm dòng nghĩa là bạn đã dựng ra một vòng lặp, và curl sẽ dừng lại để báo cho bạn biết.
Dùng 302 trong lúc còn chưa chắc chắn. Một 302 không bị cache theo kiểu đó, nên một sai lầm có thể đảo ngược trong vài giây. Chuyển sang 301 khi curl đã cho thấy đúng đích bạn muốn. Việc này chẳng tốn gì và đã cứu nhiều site hơn bất kỳ thói quen nào khác trong bài này.
Nếu một rule có vẻ chẳng làm gì cả, hãy xác nhận là Apache có đọc file đó không đã. AllowOverride None trên thư mục khiến Apache bỏ qua hoàn toàn .htaccess, còn nginx thì phớt lờ nó tuyệt đối — hãy kiểm tra header Server: bằng curl -I trước khi ngồi gỡ regex. Để dựng ra khối rule với đúng cú pháp cho phiên bản Apache và đường dẫn cài đặt của bạn, hãy dùng công cụ tạo .htaccess cho WordPress.
Khi nào thì đừng dùng .htaccess
Với một nhúm chuyển hướng mà biên tập viên phải tự quản lý, một plugin chuyển hướng lưu rule trong database là công cụ hợp lý hơn — nó sống sót qua các lần chuyển host và không đòi hỏi SSH. Cái giá phải trả là có thật: php phải khởi động lên để phục vụ chuyển hướng, chậm hơn so với việc Apache trả lời trực tiếp.
Hãy dùng .htaccess cho những lần di dời mang tính cấu trúc và vĩnh viễn — đổi tên miền, đổi tên một chuyên mục, ép https hoặc ép www. Dùng plugin cho những chuyển hướng lẻ mang tính biên tập. Làm cả hai cũng không sao, miễn là bạn biết tầng nào sở hữu URL nào, vì một chuyển hướng được định nghĩa ở hai nơi là một lỗi đang nằm chờ một buổi chiều tồi tệ.
FAQ
Câu hỏi
Các rule chuyển hướng tùy chỉnh phải đặt ở đâu trong file .htaccess của WordPress?
Đặt phía trên dòng # BEGIN WordPress, tuyệt đối không đặt giữa hai marker. WordPress tạo lại toàn bộ nội dung nằm giữa các marker đó mỗi lần có người bấm lưu ở màn hình Permalinks, nên một rule đặt bên trong sẽ bị xóa mà không hề báo trước. Vị trí còn quyết định thứ tự thực thi: khối WordPress kết thúc bằng một rule bắt tất cả, đẩy mọi request không khớp về index.php.
Nên dùng Redirect, RedirectMatch hay RewriteRule cho một 301?
Dùng Redirect cho một đường dẫn đơn lẻ đã biết, dùng RedirectMatch khi một pattern bao được nhiều URL, và dùng RewriteRule khi việc chuyển hướng phụ thuộc vào một điều kiện như hostname, giao thức hay query string. Redirect và RedirectMatch thuộc mod_alias và đơn giản hơn. RewriteRule thuộc mod_rewrite và là lựa chọn duy nhất kiểm tra được điều kiện.
Vì sao rule chuyển hướng trong .htaccess của tôi gây vòng lặp chuyển hướng?
Thường là vì đích đến vẫn khớp với chính rule đã đẩy khách tới đó, nên rule cứ chạy lại mãi. Nguyên nhân phổ biến thứ hai là ép https phía sau load balancer hoặc CDN, nơi server nhìn thấy http thuần ở mọi request dù khách đã ở trên https. Hãy kiểm tra header X-Forwarded-Proto thay vì kiểm tra biến kia.
Dấu gạch chéo cuối URL có quan trọng trong một chuyển hướng WordPress không?
Có. Cơ chế canonical redirect của WordPress thêm dấu gạch chéo cuối vào hầu hết permalink, nên trỏ một 301 tới URL không có dấu gạch chéo sẽ tạo ra hai chặng thay vì một. Chuỗi chuyển hướng vẫn truyền tín hiệu xếp hạng nhưng lãng phí crawl budget và làm khách phải chờ lâu hơn. Hãy khớp đúng URL cuối cùng mà WordPress phục vụ, kể cả dấu gạch chéo.
Làm sao test một chuyển hướng .htaccess mà không làm hỏng site?
Chạy curl với cờ chỉ lấy header lên URL cũ rồi đọc mã trạng thái và header Location trước khi tin vào trình duyệt. Trình duyệt cache phản hồi 301 rất dai và sẽ cho bạn thấy kết quả cũ. Hãy giữ một bản sao của file đang chạy tốt trước đã, vì một lỗi cú pháp trong .htaccess sẽ trả về 500 trên mọi trang, kể cả wp-admin.
Vì sao cả site trả về lỗi 500 sau khi tôi thêm một chuyển hướng?
Chỉ một directive sai định dạng trong .htaccess là đủ để hạ toàn bộ URL nằm dưới thư mục đó, kể cả trang quản trị. Nguyên nhân hay gặp là một khối IfModule chưa đóng, thiếu dòng RewriteEngine On, hoặc trộn cú pháp phân quyền của Apache 2.2 và 2.4 trong cùng một file. Hãy xóa khối bạn vừa thêm rồi tải lại để xác nhận.