ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

Home ทำ 301 Redirect บน Nginx

ทำ 301 Redirect บน Nginx

Nginx ไม่อ่านไฟล์ .htaccess การทำ 301 Redirect จึงต้องเขียนในไฟล์ตั้งค่าของ Nginx โดยตรง ข้อดีคือทำงานเร็วกว่า เพราะ Nginx ไม่ต้องอ่านไฟล์เพิ่มทุกครั้งที่มีคำขอ และตรวจไวยากรณ์ได้ก่อนนำไปใช้จริงด้วย nginx -t

บทความนี้ครอบคลุมรูปแบบที่ใช้บ่อย ตั้งแต่บังคับ HTTPS, รวมโดเมน www, ย้ายหน้าทีละหน้า, ย้ายโฟลเดอร์ ไปจนถึงการจัดการ Redirect จำนวนหลายร้อยรายการด้วย map

สิ่งที่ต้องเตรียม

  • Cloud Server ที่ติดตั้ง Nginx และผู้ใช้ที่มีสิทธิ์ sudo (ดู สร้างผู้ใช้ใหม่และให้สิทธิ์ sudo บน Linux)
  • รู้ว่าไฟล์ Server Block ของเว็บอยู่ที่ไหน: Ubuntu มักอยู่ใน /etc/nginx/sites-available/ (ลิงก์ไปยัง sites-enabled) ส่วน AlmaLinux/Rocky อยู่ใน /etc/nginx/conf.d/
  • สำเนาไฟล์ตั้งค่าเดิม เช่น sudo cp /etc/nginx/sites-available/example.com /root/example.com.bak
  • ถ้าใช้ Plesk ให้ดูหัวข้อ "กรณีใช้ Plesk" ด้านล่าง

หลักการที่ควรรู้ก่อน

  • return 301 URL; เป็นวิธีที่แนะนำ เร็วและอ่านง่าย ใช้เมื่อรู้ปลายทางแน่นอน
  • rewrite pattern replacement permanent; ใช้เมื่อต้องการดึงบางส่วนของ URL เดิมด้วย Regular Expression
  • ตัวแปร $request_uri คือ Path พร้อม Query String แบบที่ผู้ใช้ส่งมา ส่วน $uri คือ Path ที่ Nginx ทำให้เป็นมาตรฐานแล้วและไม่มี Query String
  • ใน Nginx ทุกเส้นทางขึ้นต้นด้วย / ต่างจาก .htaccess

ขั้นตอนที่ 1: บังคับ HTTPS และรวมโดเมนให้เหลือแบบเดียว

วิธีที่สะอาดที่สุดคือแยก Server Block สำหรับคำขอที่ต้องถูกส่งต่อออกจาก Server Block หลัก ตัวอย่างนี้ใช้ https://example.com เป็นโดเมนหลัก

# HTTP ทุกแบบ ไปที่ HTTPS โดเมนหลัก
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# HTTPS แบบมี www ไปที่ไม่มี www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://example.com$request_uri;
}

# Server Block หลัก
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    # ... ค่าเดิมของเว็บ ...
}

Server Block ที่ Redirect จาก https://www ต้องมีใบรับรอง SSL ที่ครอบคลุม www.example.com ด้วย ไม่เช่นนั้นเบราว์เซอร์จะแจ้งเตือนก่อนถึงขั้น Redirect ดูวิธีขอใบรับรองที่รวมทั้งสองชื่อได้ที่ ติดตั้ง SSL ฟรีจาก Let's Encrypt ด้วย Certbot บน Nginx

หมายเหตุ: รูปแบบ listen 443 ssl http2; ใช้ได้กับแพ็กเกจ Nginx มาตรฐานของ Ubuntu 22.04 (1.18) และ 24.04 (1.24) ถ้าติดตั้ง Nginx 1.25.1 ขึ้นไปจาก Repository ของ nginx.org ค่านี้ยังใช้ได้แต่ nginx -t จะเตือนว่าเลิกใช้แล้ว ให้เปลี่ยนเป็น listen 443 ssl; คู่กับบรรทัด http2 on; ตรวจเวอร์ชันได้ด้วย nginx -v

ขั้นตอนที่ 2: ย้ายหน้าทีละหน้า

ใส่ภายใน Server Block หลัก ใช้ location = ซึ่งจับคู่แบบตรงตัวและทำงานเร็วที่สุด

location = /about-us/ {
    return 301 https://example.com/about/;
}
location = /about-us {
    return 301 https://example.com/about/;
}

ต้องเขียนทั้งแบบมีและไม่มี / ปิดท้าย ถ้า URL เดิมเคยถูกเข้าถึงทั้งสองแบบ หรือใช้ Regular Expression ครั้งเดียว

location ~ ^/about-us/?$ {
    return 301 https://example.com/about/;
}

ขั้นตอนที่ 3: ย้ายทั้งโฟลเดอร์

location ^~ /blog/ {
    rewrite ^/blog/(.*)$ https://example.com/news/$1 permanent;
}

^~ ทำให้ location นี้ชนะ location แบบ Regular Expression อื่น (เช่นบล็อก ~ \.php$) คำว่า permanent ส่งรหัส 301 ถ้าใช้ redirect จะเป็น 302 เมื่อใช้ rewrite Nginx จะต่อ Query String เดิมท้าย URL ใหม่ให้อัตโนมัติ ถ้าไม่ต้องการ ให้ใส่ ? ต่อท้ายปลายทาง เช่น https://example.com/news/$1?

ขั้นตอนที่ 4: Redirect จำนวนมากด้วย map

ถ้ามี URL เดิมหลายสิบหรือหลายร้อยรายการ การเขียน location ทีละตัวจะดูแลยาก ใช้ map ซึ่งค้นหาเร็วแม้มีรายการมาก สร้างไฟล์ใหม่ที่ถูกโหลดในบริบท http ทั้ง Ubuntu และ AlmaLinux/Rocky โหลดไฟล์ใน /etc/nginx/conf.d/*.conf ภายใน http { } อยู่แล้ว

sudo nano /etc/nginx/conf.d/redirect-map.conf
map $uri $redirect_target {
    default          "";
    /old-a/          /new-a/;
    /old-b/          /products/b/;
    /promo-2024/     /promotions/;
    ~^/shop/(?<path>.+)$  /store/$path;
}

จากนั้นเพิ่มใน Server Block หลัก ด้านบนก่อน location อื่น

if ($redirect_target) {
    return 301 https://example.com$redirect_target;
}

การใช้ if คู่กับ return ใน Server Block เป็นรูปแบบที่ปลอดภัย รายการที่ขึ้นต้นด้วย ~ เป็น Regular Expression และใช้ Named Capture (?<path>) เพื่อนำค่าที่จับได้ไปใช้ในปลายทาง Nginx จะตรวจรายการแบบตรงตัวก่อนเสมอ แล้วจึงตรวจ Regular Expression ตามลำดับในไฟล์ ถ้ารายการ Redirect ยาวมากจนได้ข้อความ could not build map_hash ให้เพิ่ม map_hash_bucket_size 128; ในบริบท http

ขั้นตอนที่ 5: ตรวจไวยากรณ์และโหลดค่าใหม่

sudo nginx -t

ผลที่ถูกต้อง

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

ใช้ reload ไม่ใช่ restart เพื่อไม่ให้การเชื่อมต่อที่ค้างอยู่หลุด ถ้า nginx -t ไม่ผ่าน Nginx ตัวเดิมยังทำงานต่อด้วยค่าเก่า เว็บจะไม่ล่ม แต่ต้องแก้ให้ผ่านก่อน reload

กรณีใช้ Plesk

ไปที่ Websites & Domains เลือกโดเมน เปิด Apache & nginx Settings (บางเวอร์ชันอยู่ใต้แท็บ Hosting & DNS) แล้วใส่กฎในช่อง Additional nginx directives ช่องนี้อยู่ในบริบท server จึงใช้ location และ rewrite ได้ แต่ใช้ map ไม่ได้ ส่วนการบังคับ HTTPS บน Plesk ใช้ตัวเลือกในหน้าจอได้ ดู บังคับเว็บไซต์ให้ใช้ HTTPS บน Plesk ถ้าเปิด Proxy mode ไว้ (Nginx ส่งต่อให้ Apache) จะเขียนใน .htaccess ก็ได้ ดู ทำ 301 Redirect ด้วยไฟล์ .htaccess

ตรวจสอบผลลัพธ์

curl -sI http://www.example.com/about-us/ | grep -iE '^(HTTP|location)'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/about-us/

ใช้ -L ตามทุกทอดเพื่อดูว่ามี Redirect ซ้อนหลายทอดหรือไม่

curl -sIL http://www.example.com/about-us/ | grep -iE '^(HTTP|location)'

ถ้าเห็น 301 สองครั้งติดกัน (HTTP ไป HTTPS แล้วค่อยไปหน้าใหม่) ถือว่ายังใช้ได้ แต่ถ้าอยากให้เหลือทอดเดียว ให้ใส่ Redirect ของหน้านั้นใน Server Block พอร์ต 80 ด้วย ระหว่างทดสอบควรใช้ 302 ก่อน เพราะเบราว์เซอร์จำ 301 ไว้นาน

ปัญหาที่พบบ่อย

ERR_TOO_MANY_REDIRECTS

มักเกิดเมื่อมี Proxy หรือ CDN อยู่ด้านหน้าและส่งคำขอมาเป็น HTTP แล้วเซิร์ฟเวอร์ส่งกลับไป HTTPS ไม่รู้จบ หรือปลายทางของ Redirect ถูกจับด้วยกฎเดียวกันอีกครั้ง เช่น rewrite ^/(.*)$ /new/$1 ซึ่งจับ /new/... ซ้ำได้ ดูเรื่อง Cloudflare ที่ เลือก SSL/TLS Mode บน Cloudflare ให้ถูกต้อง

Redirect ไม่ทำงาน แต่ nginx -t ผ่าน

ตรวจว่าแก้ไฟล์ที่ถูกใช้จริง ดูค่าที่โหลดอยู่ทั้งหมดด้วย sudo nginx -T | less แล้วค้น server_name ของโดเมน บางครั้งมี Server Block อื่นที่ server_name ตรงกันและถูกเลือกก่อน ดูเพิ่มเติมที่ ตั้งค่า Nginx Server Block สำหรับหลายเว็บไซต์ในเครื่องเดียว

Location ของปลายทางมีพอร์ตแปลกๆ เช่น :8080

เกิดเมื่อใช้ปลายทางแบบ Path อย่างเดียว (return 301 /new/;) และ Nginx อยู่หลัง Proxy อีกชั้น แก้โดยใส่ URL เต็มพร้อมโดเมนเสมอ หรือตั้ง absolute_redirect off;

หากต้องการความช่วยเหลือเพิ่มเติม ติดต่อทีมงาน THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security