กฎ redirect ใน .htaccess ของ WordPress: ทำ 301 ให้ถูกต้อง
ย้าย URL โดยไม่เสียอันดับ: ควรใช้ไดเรกทีฟ redirect ตัวไหนใน .htaccess ของ WordPress ต้องวางกฎไว้ตรงไหน และทดสอบอย่างไรไม่ให้ถูกล็อกออกจากเว็บ
เผยแพร่แล้ว
กำลังย้าย URL และอยากให้ URL เดิมรักษาอันดับไว้ใช่ไหม สิ่งที่ต้องมีคือ 301 ใน .htaccess ที่วางไว้ในจุดที่ WordPress จะไม่ลบทิ้ง เขียนด้วยไดเรกทีฟที่ถูกตัว และชี้ไปยัง URL ที่ WordPress ให้บริการจริงแบบเป๊ะ ๆ พลาดข้อใดข้อหนึ่งในสามข้อนี้ คุณจะได้ redirect ต่อกันเป็นทอด ๆ ได้การวนซ้ำ หรือได้ 500 ที่ล็อกคุณออกจาก wp-admin
เรียงตามนี้: การตัดสินใจ กฎเรื่องตำแหน่ง แล้วจึงการทดสอบ
เลือกไดเรกทีฟไหน: Redirect, RedirectMatch หรือ RewriteRule
มีโมดูลของ Apache สองตัวที่เกี่ยวข้อง และทั้งสองใช้แทนกันไม่ได้
Redirect (mod_alias) — จากพาธเดียวที่รู้แน่ชัดไปยังปลายทางเดียว วิธีที่ง่ายที่สุดเท่าที่ใช้ได้ผล:
Redirect 301 /old-page/ https://example.com/new-page/
มีพฤติกรรมสองอย่างที่ต้องรู้ ตัวมันจับคู่แบบ คำนำหน้าของพาธ ไม่ใช่สตริงแบบเป๊ะ ๆ ดังนั้น /old-page/ จึงจับ /old-page/anything/ ไปด้วย แล้วต่อท้าย /anything/ ให้ปลายทาง และมันจะพา query string ไปด้วยโดยอัตโนมัติ
RedirectMatch (mod_alias) — regex เดียวที่ครอบคลุม URL จำนวนมาก นี่คือวิธีย้ายทั้งเซกชัน:
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1
กลุ่มจับค่า (.*) จะกลายเป็น $1 ในปลายทาง ดังนั้น /blog/hello-world/ จึงไปลงที่ /articles/hello-world/ สังเกตเครื่องหมายทับนำหน้าด้วย เพราะรูปแบบของ mod_alias จับคู่กับพาธเต็มของ URL
RewriteRule (mod_rewrite) — จำเป็นเมื่อการ redirect ขึ้นอยู่กับ เงื่อนไข ไม่ว่าจะเป็น hostname โปรโตคอล query string หรือ user agent ซึ่งไม่มีอะไรใน 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 เฉพาะตอนที่ต้องใช้เงื่อนไขจริง ๆ regex น้อยลง โอกาสผิดก็น้อยลง
ต้องวางกฎไว้ตรงไหน
redirect ที่เขียนเองต้องอยู่ เหนือ บรรทัด # 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 เขียนบล็อกของตัวเองใหม่ การบันทึกที่ Settings → Permalinks จะเรียก
flush_rewrite_rules()ซึ่งทิ้งทุกอย่างระหว่างมาร์กเกอร์แล้วสร้างขึ้นใหม่ กฎที่คุณใส่ไว้ข้างในจะหายไปในครั้งถัดไปที่ใครก็ตามแตะหน้าจอนั้น ซึ่งอาจเป็นอีกหลายเดือนให้หลัง โดยไม่มีใครเชื่อมโยงสองเหตุการณ์นี้เข้าด้วยกัน - ลำดับเป็นตัวตัดสินว่าใครชนะ บล็อกของ WordPress จบด้วยกฎดักจับทั้งหมดที่ส่งทุกคำขอซึ่งไม่ใช่ไฟล์และไม่ใช่ไดเรกทอรีไปยัง
index.phpRewriteRuleที่วางไว้หลังจากนั้นจึงไม่มีวันทำงาน
มีข้อซับซ้อนหนึ่งที่ต้องพูดกันตรง ๆ คือ mod_alias กับ mod_rewrite ไม่ได้ทำงานตามลำดับในไฟล์จริง ๆ Apache ประมวลผล mod_alias ระหว่างขั้นแปลง URL และประมวลผล mod_rewrite ระดับไดเรกทอรีทีหลังในขั้น fixup ดังนั้นบรรทัด Redirect จึงอาจชนะ RewriteRule ที่อยู่เหนือมันในไฟล์ได้ ถ้าพบว่าตัวเองต้องใช้ทั้งสองอย่างบนพาธที่ทับซ้อนกัน ให้เลือกโมดูลเดียวสำหรับพาธนั้นแล้วอยู่กับมันไป การไล่แก้ศึกระหว่าง mod_alias กับ mod_rewrite ไม่คุ้มกับเวลาที่เสียไปหนึ่งชั่วโมง
เครื่องหมายทับปิดท้าย
ระบบ redirect เชิง canonical ของ WordPress เองจะเติมเครื่องหมายทับปิดท้ายให้ permalinks ส่วนใหญ่ ดังนั้นแบบนี้:
Redirect 301 /old-page/ https://example.com/new-page
จะเกิดสองต่อ คือ 301 ของคุณไปยัง URL ที่ไม่มีทับปิดท้าย แล้วตามด้วย 301 ของ WordPress เองที่เติมทับเข้าไป การต่อกันแบบนี้ยังส่งสัญญาณอันดับได้อยู่ แต่เปลืองงบการรวบรวมข้อมูลและเพิ่มรอบการเดินทางอีกหนึ่งรอบให้ผู้เข้าชมทุกคน
ให้เปิดปลายทางในเบราว์เซอร์ก่อน คัดลอก URL ตามที่มันนิ่งอยู่ในแถบที่อยู่แบบเป๊ะ ๆ แล้วใช้ตัวนั้น ถ้าโครงสร้าง permalink ของคุณลงท้ายด้วย .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
ให้อ่านสองอย่าง คือบรรทัด status ต้องขึ้น 301 และเฮดเดอร์ Location: ต้องเป็น URL สุดท้ายแบบเป๊ะ ๆ ถ้าอยากเห็นทั้งสาย ก็ตามมันไป:
curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'
ถ้าเห็นบรรทัด HTTP/1.1 301 มากกว่าหนึ่งบรรทัด แปลว่าคุณสร้างสายต่อกันขึ้นมา ถ้ามากกว่าราวห้าบรรทัด แปลว่าคุณสร้างการวนซ้ำ และ curl จะหยุดพร้อมบอกคุณเอง
ระหว่างที่ยังไม่แน่ใจ ให้ใช้ 302 ไปก่อน เพราะ 302 ไม่ถูกแคชแบบเดียวกัน ความผิดพลาดจึงย้อนกลับได้ในไม่กี่วินาที ค่อยเปลี่ยนเป็น 301 เมื่อ curl แสดงปลายทางตามที่คุณตั้งใจ วิธีนี้ไม่มีต้นทุนอะไรเลย และช่วยกู้เว็บไซต์ไว้ได้มากกว่านิสัยอื่นใดในบทความนี้
ถ้ากฎดูเหมือนไม่ทำอะไรเลย ให้ยืนยันก่อนว่า Apache อ่านไฟล์นั้นอยู่จริงหรือเปล่า การตั้ง AllowOverride None บนไดเรกทอรีทำให้ Apache เมิน .htaccess ทั้งไฟล์ ส่วน nginx ไม่สนใจมันเลยแม้แต่น้อย ให้ตรวจเฮดเดอร์ Server: ด้วย curl -I ก่อนจะไปนั่งไล่ regex และหากต้องการประกอบบล็อกด้วยไวยากรณ์ที่ตรงกับเวอร์ชัน Apache และพาธติดตั้งของคุณ ให้ใช้ ตัวสร้าง .htaccess สำหรับ WordPress
เมื่อไรที่ไม่ควรใช้ .htaccess เลย
สำหรับ redirect ไม่กี่รายการที่ทีมบรรณาธิการต้องจัดการเอง ปลั๊กอิน redirect ที่เก็บกฎไว้ในฐานข้อมูลคือเครื่องมือที่เหมาะกว่า เพราะมันอยู่รอดข้ามการย้ายโฮสต์และไม่ต้องใช้ SSH แต่ก็มีข้อแลกเปลี่ยนจริง ๆ คือ php ต้องบูตขึ้นมาเพื่อให้บริการ redirect ซึ่งช้ากว่าให้ Apache ตอบตรง ๆ
ใช้ .htaccess กับการย้ายเชิงโครงสร้างที่ถาวร เช่น เปลี่ยนโดเมน เปลี่ยนชื่อเซกชัน บังคับ https หรือ www ส่วนปลั๊กอินให้ใช้กับ redirect เชิงบรรณาธิการที่เกิดเป็นครั้งคราว จะทำทั้งสองอย่างก็ไม่เสียหาย ตราบใดที่คุณรู้ว่าชั้นไหนเป็นเจ้าของ URL ไหน เพราะ redirect ที่ถูกกำหนดไว้สองที่คือบั๊กที่รอวันปะทุในบ่ายวันที่แย่ ๆ สักวัน
FAQ
คำถาม
ต้องวาง redirect ที่เขียนเองไว้ตรงไหนในไฟล์ .htaccess ของ WordPress
วางไว้เหนือบรรทัด # BEGIN WordPress ห้ามวางระหว่างมาร์กเกอร์เด็ดขาด เพราะทุกครั้งที่มีคนกดบันทึกหน้า Permalinks ทาง WordPress จะสร้างทุกอย่างระหว่างมาร์กเกอร์ขึ้นใหม่ กฎที่วางไว้ข้างในจึงถูกลบทิ้งโดยไม่มีการเตือน ตำแหน่งยังสำคัญต่อลำดับการทำงานด้วย เพราะบล็อกของ WordPress จบด้วยกฎดักจับทั้งหมดที่ส่งคำขอที่ไม่เข้าเงื่อนไขไปยัง index.php
สำหรับ 301 ควรใช้ Redirect, RedirectMatch หรือ RewriteRule
ใช้ Redirect เมื่อรู้พาธเดียวแน่ชัด ใช้ RedirectMatch เมื่อรูปแบบเดียวครอบคลุม URL ได้หลายรายการ และใช้ RewriteRule เมื่อการ redirect ขึ้นอยู่กับเงื่อนไข เช่น hostname โปรโตคอล หรือ query string โดย Redirect และ RedirectMatch มาจาก mod_alias และเรียบง่ายกว่า ส่วน RewriteRule มาจาก mod_rewrite และเป็นตัวเดียวที่ตรวจสอบเงื่อนไขได้
ทำไม redirect ใน .htaccess ของผมถึงทำให้เกิด redirect วนซ้ำ
ส่วนใหญ่เป็นเพราะปลายทางยังเข้าเงื่อนไขของกฎที่ส่งผู้เข้าชมมาที่นั่น กฎจึงทำงานซ้ำไม่รู้จบ อีกสาเหตุที่พบบ่อยคือการบังคับ https หลังโหลดบาลานเซอร์หรือ CDN ซึ่งเซิร์ฟเวอร์จะเห็นทุกคำขอเป็น http ธรรมดา ทั้งที่ผู้เข้าชมอยู่บน https อยู่แล้ว ให้เปลี่ยนไปตรวจสอบเฮดเดอร์ X-Forwarded-Proto แทน
เครื่องหมายทับปิดท้ายมีผลกับ redirect ของ WordPress ไหม
มีผล เพราะระบบ redirect เชิง canonical ของ WordPress จะเติมเครื่องหมายทับปิดท้ายให้ permalinks ส่วนใหญ่ การชี้ 301 ไปยัง URL ที่ไม่มีทับปิดท้ายจึงทำให้เกิดสองต่อแทนที่จะเป็นต่อเดียว การ redirect ต่อกันยังส่งสัญญาณอันดับได้ก็จริง แต่เปลืองงบการรวบรวมข้อมูลและทำให้ผู้เข้าชมช้าลง ควรจับคู่กับ URL สุดท้ายที่ WordPress ให้บริการจริงแบบเป๊ะ ๆ รวมถึงเครื่องหมายทับด้วย
จะทดสอบ redirect ใน .htaccess อย่างไรไม่ให้เว็บพัง
ก่อนจะเชื่อเบราว์เซอร์ ให้รัน curl พร้อมแฟล็กที่ขอเฉพาะส่วนหัวไปยัง URL เดิม แล้วอ่าน status code กับเฮดเดอร์ Location เพราะเบราว์เซอร์แคชการตอบกลับ 301 อย่างหนักและจะแสดงผลเก่าให้เห็น อย่าลืมสำรองไฟล์ที่ใช้งานได้ไว้ก่อน เพราะไวยากรณ์ผิดเพียงจุดเดียวใน .htaccess จะทำให้ทุกหน้ารวมถึง wp-admin ตอบกลับเป็น 500
ทำไมทั้งเว็บถึงขึ้นข้อผิดพลาด 500 หลังจากเพิ่ม redirect
ไดเรกทีฟที่เขียนผิดเพียงบรรทัดเดียวใน .htaccess ทำให้ทุก URL ใต้ไดเรกทอรีนั้นล่ม รวมถึงหน้าแอดมิน สาเหตุที่พบบ่อยคือครอบ IfModule แล้วไม่ปิด ลืมบรรทัด RewriteEngine On หรือปนไวยากรณ์การเข้าถึงของ Apache 2.2 กับ 2.4 ไว้ในไฟล์เดียวกัน ให้ลบบล็อกที่เพิ่งเพิ่มออกแล้วโหลดใหม่เพื่อยืนยัน