ลำดับ location ใน Nginx: บล็อกไหนชนะ ทดลองด้วย curl
ทำไม config Nginx ที่ควรเข้า location หนึ่งกลับไปตกอีกบล็อก? ลองแล็บบน Ubuntu VPS ที่ทุกบล็อกตอบชื่อตัวเอง แล้วให้ curl พิสูจน์ลำดับ =, ^~, regex และกับดัก try_files
Nginx เลือก location บล็อกไหน: กฎสี่ขั้นจาก nginx.org
Nginx เลือก location ให้แต่ละ request เพียงบล็อกเดียว และลำดับที่บล็อกเขียนอยู่ในไฟล์มีผลกับ regex เท่านั้น เอกสาร location ของ nginx.org อธิบายการเลือกไว้สี่ขั้น:
- nginx หา
location =ที่ตรงกับ URI ทุกตัวอักษร ถ้าเจอ ใช้บล็อกนั้นและหยุดค้นหาทันที - nginx ตรวจ prefix location ทุกบล็อก แล้วจำบล็อกที่ prefix ยาวที่สุดไว้ ถ้าบล็อกนั้นมี
^~nginx ใช้บล็อกนั้นเลยโดยไม่ดู regex - nginx ตรวจ regex ตามลำดับที่เขียนในไฟล์
~แยกตัวพิมพ์เล็กกับตัวพิมพ์ใหญ่ ส่วน~*ไม่แยก regex ตัวแรกที่ตรงคือบล็อกที่ใช้ - ถ้าไม่มี regex ไหนตรงเลย nginx ใช้ prefix ที่ยาวที่สุดซึ่งจำไว้ในขั้นที่ 2
URI (uniform resource identifier) ในที่นี้หมายถึงส่วน path ของ URL เช่น /images/cat.png ส่วน regex (regular expression) คือรูปแบบข้อความที่ใช้จับคู่ URI และ prefix คือข้อความช่วงต้นของ URI ที่ต้องตรงกับที่เขียนไว้หลังคำว่า location
ถ้าคุณมาที่หน้านี้เพราะ config มีบล็อกที่ “ควรจะตรง” แต่ request กลับไปโผล่ที่บล็อกอื่น กฎสี่ขั้นนี้คือจุดเริ่ม แต่อ่านกฎอย่างเดียวไม่พอ config จริงมักพังที่รายละเอียด เช่น ^~ มีผลตอนไหน หรือ regex ตัวไหนแย่ง request ไปก่อน เราจึงจะสร้างแล็บบน Ubuntu VPS ที่ทุก location ตอบชื่อตัวเองกลับมา แล้วตั้งคำถามทีละข้อ ให้ curl เป็นคนตัดสิน ไม่ต้องเชื่อคำอธิบายของใคร
เตรียมแล็บบน Ubuntu VPS
แล็บนี้ใช้ nginx จาก archive ของ Ubuntu ไม่ต้องเพิ่ม repository ภายนอก server block ของแล็บฟังที่ 127.0.0.1:8080 ซึ่งรับเฉพาะ request ที่มาจากตัวเครื่องเอง จึงไม่ต้องเปิด firewall และไม่ยุ่งกับเว็บที่รันอยู่บนพอร์ต 80 หรือ 443 ทุกคำสั่งข้างล่างรันบน VPS ผ่าน SSH
sudo apt update
sudo apt install -y nginx curl
nginx -vnginx -v พิมพ์บรรทัดเดียวในรูปแบบ nginx version: nginx/1.x.y (Ubuntu) เลขเวอร์ชันขึ้นกับรุ่น Ubuntu ที่คุณใช้ จดไว้ เผื่อต้องเทียบผลกับเครื่องอื่น ถ้าเครื่องนี้ยังไม่เคยมี nginx แพ็กเกจของ Ubuntu จะเปิด service ให้ทันที พร้อมเว็บ default บนพอร์ต 80
ก่อนสร้าง config ให้ตรวจว่าพอร์ต 8080 ว่าง:
sudo ss -ltnp | grep ':8080 'ถ้าคำสั่งนี้ไม่พิมพ์อะไรออกมา แปลว่าพอร์ตว่าง ถ้ามีบรรทัดออกมา ให้เปลี่ยน 8080 ในทุกคำสั่งข้างล่างเป็นพอร์ตอื่น เพราะ nginx ไม่สามารถ bind พอร์ตที่โปรแกรมอื่นถืออยู่ได้
สร้างโฟลเดอร์ของแล็บและไฟล์ว่างหนึ่งไฟล์:
sudo mkdir -p /srv/location-lab/docs
sudo touch /srv/location-lab/docs/index.phpไฟล์ index.php นี้ว่างเปล่า และเครื่องไม่ต้องติดตั้ง PHP เลย เราจะใช้มันในหัวข้อกับดักตอนท้าย
จากนั้นเขียน config ของแล็บ ทุก location ตอบด้วย return 200 และชื่อของตัวเอง:
sudo tee /etc/nginx/conf.d/location-lab.conf > /dev/null <<'EOF'
server {
listen 127.0.0.1:8080;
server_name location-lab;
root /srv/location-lab;
default_type text/plain;
location = / {
return 200 "matched: = /\n";
}
location / {
return 200 "matched: / (prefix)\n";
}
location /app {
return 200 "matched: /app (prefix)\n";
}
location ^~ /images/ {
return 200 "matched: ^~ /images/\n";
}
location /images/thumbs/ {
return 200 "matched: /images/thumbs/ (prefix)\n";
}
location /static/ {
return 200 "matched: /static/ (prefix)\n";
}
# regex A
location ~* \.(png|jpg)$ {
return 200 "matched: regex A (~* png or jpg)\n";
}
# regex B
location ~ ^/static/.*\.png$ {
return 200 "matched: regex B (~ png under /static/)\n";
}
location /blog/ {
try_files $uri $uri/ /blog/index.php;
}
location /docs/ {
index index.php;
}
location ~ \.php$ {
return 200 "matched: regex php, uri=$uri\n";
}
}
EOFเครื่องหมายคำพูดรอบ 'EOF' สำคัญ เพราะมันสั่งให้ shell เขียนข้อความลงไฟล์ตามตัวอักษร ถ้าเขียน EOF เฉยๆ shell จะแทน $uri ด้วยค่าว่างก่อนเขียน ไฟล์ที่ได้จึงต่างจากที่คุณเห็น ไฟล์ /etc/nginx/nginx.conf ของ Ubuntu มีบรรทัด include /etc/nginx/conf.d/*.conf; อยู่ใน http แล้ว ไฟล์นี้จึงถูกโหลดโดยไม่ต้องสร้าง symlink ใน sites-enabled
ตรวจ config แล้วค่อย reload:
sudo nginx -t && sudo systemctl reload nginxผลที่ถูกต้องของ nginx -t คือสองบรรทัดนี้:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful&& ทำให้ reload ทำงานเฉพาะเมื่อการตรวจผ่าน ทำแบบนี้ทุกครั้งที่แก้ไฟล์ ถ้า config มีข้อผิดพลาด nginx ที่รันอยู่จะใช้ config เดิมต่อไป ผลคือคุณทดสอบ config เก่าโดยไม่รู้ตัว และถ้าใช้ restart แทน reload ตอน config เสีย nginx จะ start ไม่ขึ้น ทุกเว็บบนเครื่องจะหยุดตอบ
สุดท้าย สร้างฟังก์ชันสั้นๆ สำหรับยิง request หลายตัวในคำสั่งเดียว:
t() { for p in "$@"; do printf '%s -> ' "$p"; curl -s "http://127.0.0.1:8080$p"; done; }ฟังก์ชัน t อยู่เฉพาะใน shell ที่เปิดอยู่ ถ้าเปิด SSH ใหม่ ต้องวางบรรทัดนี้อีกครั้ง
location = / กับ location /: request ไปที่ / ตกบล็อกไหน
config มีทั้ง location = / และ location / คำถามคือ request ไปที่ / กับ /index.html ตกบล็อกเดียวกันหรือไม่
t / /index.html/ -> matched: = /
/index.html -> matched: / (prefix)= ต้องการให้ URI เท่ากันทุกตัวอักษร / ตรงพอดี nginx จึงหยุดตั้งแต่ขั้นแรก ส่วน /index.html ไม่เท่ากับ / จึงไปขั้นที่สอง prefix / ตรงกับทุก URI และไม่มี regex ไหนจับ .html คำตอบจึงมาจาก location /
location / จึงทำหน้าที่เป็นบล็อกสำรองของ server ทุก request ที่ไม่มีบล็อกอื่นรับจะมาจบที่นี่ ส่วน location = / เหมาะกับหน้าแรกที่มีคนเรียกบ่อย เพราะ nginx หยุดค้นหาเร็วที่สุด เอกสารของ nginx.org ก็ยกตัวอย่างนี้ไว้
location /app จับ /application ด้วยหรือไม่
บล็อก location /app เขียนไว้สำหรับแอปที่อยู่ใต้ /app คำถามคือมันรับ URI อื่นที่บังเอิญขึ้นต้นเหมือนกันด้วยหรือเปล่า
t /app /app/ /application /ap/app -> matched: /app (prefix)
/app/ -> matched: /app (prefix)
/application -> matched: /app (prefix)
/ap -> matched: / (prefix)prefix เทียบกันทีละตัวอักษร ไม่ได้เทียบทีละส่วนของ path /application ขึ้นต้นด้วยตัวอักษร /app จึงนับว่าตรง ส่วน /ap สั้นกว่า prefix จึงไม่ตรง และไปจบที่ location /
นี่คือที่มาของบั๊กที่พบบ่อย: เขียน location /api เพื่อส่งต่อให้ backend แล้ว /api-docs หรือ /apidata ถูกส่งไปด้วย ถ้าต้องการเฉพาะ /app และทุกอย่างใต้ /app/ ให้เขียนสองบล็อก:
location = /app {
return 301 /app/;
}
location /app/ {
return 200 "matched: /app/ (prefix)\n";
}= /app รับ URI ที่ไม่มี / ท้าย แล้วส่ง redirect ไปที่ /app/ ส่วน /app/ รับทุกอย่างข้างใต้ /application จะไม่ตรงทั้งสองบล็อกอีกต่อไป ถ้าบล็อก /app/ ใช้ proxy_pass และไม่มีบล็อก = /app เอกสาร nginx.org ระบุว่า nginx จะส่ง redirect 301 สำหรับ /app ให้เอง แต่เครื่องหมาย / ท้าย proxy_pass เป็นอีกเรื่องหนึ่ง มันเปลี่ยน URI ที่ backend ได้รับ อ่านเรื่องนั้นได้ที่ คำอธิบาย config ของ Nginx reverse proxy และ / ท้าย proxy_pass
^~ /images/ ที่สั้นกว่า ชนะ /images/thumbs/ ที่ยาวกว่าได้ไหม
หลายคนอ่าน ^~ ว่า “บล็อกนี้สำคัญกว่า” ลองดูว่าจริงไหม config มี ^~ /images/ ซึ่งสั้นกว่า และ /images/thumbs/ แบบธรรมดาซึ่งยาวกว่า นอกจากนี้ยังมี regex A ที่จับไฟล์ .png และ .jpg ทุกที่ คำถามคือ /images/thumbs/cat.png ตกบล็อกไหน
t /images/cat.png /images/thumbs/ /images/thumbs/cat.png/images/cat.png -> matched: ^~ /images/
/images/thumbs/ -> matched: /images/thumbs/ (prefix)
/images/thumbs/cat.png -> matched: regex A (~* png or jpg)/images/thumbs/cat.png ไม่ได้ตกที่ prefix บล็อกไหนเลย มันตกที่ regex A เหตุผลอยู่ในขั้นที่สอง: nginx หา prefix ที่ยาวที่สุดก่อน แล้วจึงดูว่าบล็อกนั้นมี ^~ หรือไม่ บล็อกที่ยาวที่สุดคือ /images/thumbs/ ซึ่งไม่มี ^~ nginx จึงไปตรวจ regex ต่อ และ regex A ตรงกับ .png
ส่วน /images/cat.png มี prefix ที่ยาวที่สุดคือ ^~ /images/ nginx จึงหยุดที่บล็อกนั้นและไม่ดู regex เลย ถึงแม้ regex A จะตรงกับ .png ก็ตาม /images/thumbs/ ไม่มีนามสกุลไฟล์ จึงไม่มี regex ไหนตรง และกลับไปใช้ prefix ที่ยาวที่สุดตามขั้นที่สี่
สรุปสั้นๆ: ^~ ไม่ได้เพิ่มลำดับความสำคัญของบล็อก มันบอกเพียงว่า “ถ้าบล็อกนี้เป็น prefix ที่ยาวที่สุด ไม่ต้องดู regex” ถ้าต้องการให้ทุกอย่างใต้ /images/ หลบ regex ทุก prefix ที่ยาวกว่าซึ่งอยู่ใต้ /images/ ต้องมี ^~ ด้วย
กรณีนี้เจอบ่อยใน config จริงสองแบบ แบบแรกคือ static file เช่นในงาน deploy Django ด้วย Gunicorn และ Nginx บน VPS ถ้าคุณเพิ่ม regex สำหรับตั้ง cache ของไฟล์ .css และ .js ไว้ใน server เดียวกัน request ไปที่ /static/app.css จะตกที่ regex นั้น ไม่ใช่บล็อก location /static/ ที่มี alias ผลที่พบบ่อยคือ 404 เพราะบล็อก regex ใช้ root ของ server ซึ่งไม่ได้ชี้ไปที่โฟลเดอร์ static ใส่ ^~ ให้ /static/ แล้ว regex จะไม่แย่งอีก
แบบที่สองคือ location ~ /\. { deny all; } ที่ใช้กันไฟล์ซ่อนอย่าง .git regex นี้จับ /.well-known/acme-challenge/ ไปด้วย ซึ่งเป็น path ที่ Let's Encrypt ใช้ตรวจความเป็นเจ้าของโดเมนผ่าน ACME (automatic certificate management environment) ถ้าคุณออกใบรับรองแบบ webroot การตรวจจะล้มเหลวเพราะได้ 403 กลับไป ทางแก้คือเพิ่ม location ^~ /.well-known/acme-challenge/ ดูขั้นตอนออกใบรับรองทั้งหมดได้ที่ การติดตั้ง Certbot และ Let's Encrypt กับ Nginx บน Ubuntu 24.04
regex ตัวแรกในไฟล์ชนะ regex ที่เจาะจงกว่าหรือเปล่า
regex A จับ .png ทุกที่ regex B จับเฉพาะ .png ใต้ /static/ ซึ่งเจาะจงกว่า ในไฟล์ regex A อยู่ก่อน คำถามคือ /static/logo.png ตกที่ regex ไหน
t /static/logo.css /static/logo.png /static/LOGO.PNG/static/logo.css -> matched: /static/ (prefix)
/static/logo.png -> matched: regex A (~* png or jpg)
/static/LOGO.PNG -> matched: regex A (~* png or jpg)regex A ชนะ ทั้งที่ regex B เจาะจงกว่า nginx ไม่วัดว่า regex ไหนเจาะจงกว่า มันตรวจทีละตัวจากบนลงล่าง แล้วหยุดที่ตัวแรกที่ตรง ส่วน /static/logo.css ไม่ตรง regex ใดเลย จึงใช้ prefix /static/
ลองสลับลำดับ เปิดไฟล์ด้วย sudo nano /etc/nginx/conf.d/location-lab.conf ย้ายบล็อกใต้ # regex B ทั้งบล็อกขึ้นไปไว้เหนือ # regex A บันทึกไฟล์ แล้วตรวจ reload และยิงชุดเดิม:
sudo nginx -t && sudo systemctl reload nginx
t /static/logo.css /static/logo.png /static/LOGO.PNG/static/logo.css -> matched: /static/ (prefix)
/static/logo.png -> matched: regex B (~ png under /static/)
/static/LOGO.PNG -> matched: regex A (~* png or jpg)ตอนนี้ regex B อยู่ก่อน จึงได้ /static/logo.png ไป แต่ /static/LOGO.PNG ยังตกที่ regex A เพราะ regex B ใช้ ~ ซึ่งแยกตัวพิมพ์เล็กกับตัวพิมพ์ใหญ่ .PNG จึงไม่ตรงกับ \.png$ nginx ข้ามไปตรวจ regex A ซึ่งใช้ ~* และตรง
“ลำดับในไฟล์” หมายถึงลำดับหลังจาก nginx รวมไฟล์ที่ include เข้ามาแล้ว regex ที่อยู่ใน snippet อาจอยู่ก่อนบล็อกที่คุณเขียนเองโดยที่คุณไม่ได้สังเกต ดูลำดับจริงได้จาก sudo nginx -T ซึ่งพิมพ์ config ทั้งหมดที่ nginx โหลด ตามลำดับที่ nginx อ่าน
nginx เทียบ location กับ URI แบบไหน
คำถามต่อไป: query string, ตัวอักษรที่ถูก encode เป็น %XX, เครื่องหมาย / ซ้อนกัน และ .. ใน path มีผลกับการเลือก location หรือไม่
t '/app?file=cat.png' /st%61tic/logo.css /images//cat.png
curl -s --path-as-is http://127.0.0.1:8080/images/thumbs/../cat.png/app?file=cat.png -> matched: /app (prefix)
/st%61tic/logo.css -> matched: /static/ (prefix)
/images//cat.png -> matched: ^~ /images/
matched: ^~ /images/query string ไม่อยู่ในการเทียบ cat.png หลัง ? จึงไม่ทำให้ regex A ตรง nginx ถอด %61 เป็นตัว a ก่อนเทียบ /st%61tic/ จึงกลายเป็น /static/ ค่า merge_slashes ซึ่งเปิดอยู่โดย default รวม // เป็น / เดียว และ nginx แก้ .. ออกก่อนเทียบ /images/thumbs/../cat.png จึงกลายเป็น /images/cat.png ซึ่งตกที่ ^~ /images/ เอกสาร nginx.org เรียก URI ที่ผ่านขั้นตอนนี้แล้วว่า normalized URI
ตัวเลือก --path-as-is บอกให้ curl ส่ง .. ไปตามที่พิมพ์ เพราะปกติ curl ตัด .. ออกเองก่อนส่ง ถ้าไม่ใส่ตัวเลือกนี้ คุณจะกำลังทดสอบ curl ไม่ใช่ nginx
กับดัก: try_files และ index ทำให้ nginx เลือก location ใหม่
กรณีนี้คือสิ่งที่ทำให้ config จริงสับสนที่สุด คำถาม: request ไปที่ /blog/hello ควรตกที่ location /blog/ ใช่ไหม และ /docs/ ควรตกที่ location /docs/ ใช่ไหม
t /blog/hello /docs//blog/hello -> matched: regex php, uri=/blog/index.php
/docs/ -> matched: regex php, uri=/docs/index.phpทั้งสอง request ตอบจาก location ~ \.php$ ทั้งที่ URL ไม่มี .php และเครื่องไม่มี PHP ด้วยซ้ำ ค่า uri= บอกสาเหตุ: URI ถูกเปลี่ยนไปแล้ว
รอบแรก nginx เลือก location /blog/ ถูกต้อง แต่ try_files ไม่พบไฟล์ /srv/location-lab/blog/hello และไม่พบโฟลเดอร์ชื่อนั้น จึงใช้พารามิเตอร์ตัวสุดท้าย ซึ่งเป็น URI การไปที่ URI นั้นเรียกว่า internal redirect: nginx เปลี่ยน URI ภายในเครื่อง แล้วเลือก location ใหม่ตั้งแต่ขั้นแรก client ไม่เห็น redirect นี้ รอบที่สอง URI คือ /blog/index.php prefix ที่ยาวที่สุดยังเป็น /blog/ แต่บล็อกนั้นไม่มี ^~ nginx จึงตรวจ regex และ \.php$ ตรง
index ทำแบบเดียวกัน nginx ตรวจไฟล์ตามรายชื่อใน index พบ /srv/location-lab/docs/index.php แล้ว redirect ภายในไปที่ /docs/index.php นี่คือเหตุผลที่เราสร้างไฟล์ว่างไว้ตอนต้น เอกสาร nginx.org ของ index เขียนไว้ตรงๆ ว่าการใช้ index file ทำให้เกิด internal redirect และ request อาจถูกประมวลผลใน location อื่น
ผลที่ตามมาคือ ค่าที่ตั้งไว้ใน location /blog/ เช่น add_header หรือ expires ไม่มีผลกับคำตอบ เพราะบล็อกที่ตอบจริงคือบล็อก regex บนเว็บ PHP นี่คือพฤติกรรมที่ตั้งใจ try_files $uri $uri/ /index.php?$args; มีไว้เพื่อส่งต่อให้บล็อก PHP แต่ถ้าคุณคาดว่าค่าในบล็อกแรกยังทำงานอยู่ หรือมี regex อื่นที่ไม่เกี่ยวข้องมาจับ URI ใหม่ คุณจะได้ผลที่ดูไม่มีเหตุผล
ถ้าต้องการให้ fallback ไปที่บล็อกที่คุณกำหนดเอง ให้ใช้ named location เปิดไฟล์อีกครั้ง แล้วแทนบล็อก location /blog/ เดิมด้วยสองบล็อกนี้:
location /blog/ {
try_files $uri $uri/ @blog;
}
location @blog {
return 200 "matched: @blog, uri=$uri\n";
}sudo nginx -t && sudo systemctl reload nginx
t /blog/hello/blog/hello -> matched: @blog, uri=/blog/hellonamed location คือบล็อกที่ชื่อขึ้นต้นด้วย @ nginx ส่ง request ไปที่บล็อกนั้นโดยตรง ไม่เลือก location ใหม่ และ URI ไม่เปลี่ยน อีกทางหนึ่งคือเพิ่ม location = /blog/index.php เพราะ = ชนะ regex ในการเลือกรอบที่สอง
ใน config จริง จะรู้ได้อย่างไรว่า request ตกบล็อกไหน
ในแล็บเราใช้ return ได้ เพราะไม่มีเว็บจริงอยู่ข้างหลัง บน server จริงให้ไล่ตามขั้นข้างล่าง
ตรวจว่า request ไปถึง server block ที่ถูกต้อง location ถูกเลือกภายใน server block ที่ nginx เลือกไว้แล้วเท่านั้น ถ้า server_name ไม่ตรง request จะไปตก default_server และบล็อกที่คุณแก้จะไม่ถูกพิจารณาเลย ทดสอบจากตัว VPS เองโดยกำหนด header Host แทน example.com และ /path ด้วยค่าของคุณ:
curl -sI -H 'Host: example.com' http://127.0.0.1/pathดู config ที่ nginx โหลดจริง คำสั่งนี้พิมพ์ชื่อไฟล์ทุกไฟล์ที่ถูก include พร้อมบรรทัด server_name และ location ตามลำดับที่ nginx อ่าน:
sudo nginx -T 2>/dev/null | grep -nE 'configuration file|server_name|location'ติดป้าย header ชั่วคราว เพิ่มบรรทัดนี้ในบล็อกที่สงสัย โดยเปลี่ยนชื่อให้ต่างกันในแต่ละบล็อก แล้วตรวจและ reload:
add_header X-Debug-Location "blog" always;curl -sI -H 'Host: example.com' http://127.0.0.1/path | grep -i x-debug-locationบรรทัดที่ได้บอกชื่อบล็อกที่ตอบ always ทำให้ header ออกแม้คำตอบเป็น 404 หรือ 500 มีข้อควรระวังหนึ่งข้อ: ตามเอกสาร nginx.org add_header สืบทอดจากระดับ server เฉพาะเมื่อบล็อกนั้นไม่มี add_header ของตัวเองเลย การเพิ่ม header ดีบักจึงทำให้ header ระดับ server เช่น security header หายไปจากบล็อกนั้นชั่วคราว ลบบรรทัดดีบักออกทันทีที่หาคำตอบได้
ตรวจว่า reload สำเร็จจริง ถ้า sudo nginx -t ไม่ผ่าน nginx ที่รันอยู่ยังใช้ config เก่า การแก้ของคุณจึงยังไม่มีผล
ลบแล็บเมื่อทดลองเสร็จ
sudo rm /etc/nginx/conf.d/location-lab.conf
sudo rm -r /srv/location-lab
sudo nginx -t && sudo systemctl reload nginxคำสั่งชุดนี้ลบเฉพาะแล็บ nginx และเว็บอื่นบนเครื่องยังทำงานตามเดิม ถ้าคุณติดตั้ง nginx มาเพื่อแล็บนี้อย่างเดียว ใช้ sudo apt remove nginx ได้
กฎการเลือกเส้นทางเป็นเรื่องเฉพาะของแต่ละโปรแกรม Caddy และ Traefik ใช้กฎของตัวเอง ถ้าคุณกำลังชั่งว่าจะใช้ nginx ต่อหรือย้ายไปตัวอื่น ดู การเปรียบเทียบ Nginx, Caddy และ Traefik สำหรับงาน reverse proxy ก่อนตัดสินใจ
FAQ
Nginx location แบบ prefix กับแบบ regex ตัวไหนชนะ?
ขึ้นกับว่า prefix ที่ยาวที่สุดมี ^~ หรือไม่ nginx หา prefix ที่ยาวที่สุดก่อน ถ้าบล็อกนั้นมี ^~ จะใช้บล็อกนั้นทันที ถ้าไม่มี nginx ตรวจ regex ตามลำดับในไฟล์ และ regex ตัวแรกที่ตรงจะชนะ prefix จะถูกใช้ก็ต่อเมื่อไม่มี regex ไหนตรงเลย ส่วน location = ที่ตรงกับ URI ทุกตัวอักษรชนะทุกแบบ
ใส่ ^~ แล้ว ทำไม regex ยังแย่ง request ไปได้?
เพราะ ^~ มีผลเฉพาะเมื่อบล็อกนั้นเป็น prefix ที่ยาวที่สุดที่ตรงกับ URI ถ้ามีบล็อก prefix อื่นที่ยาวกว่าและไม่มี ^~ เช่นมีทั้ง ^~ /images/ และ /images/thumbs/ request ไปที่ /images/thumbs/cat.png จะได้ /images/thumbs/ เป็น prefix ที่ยาวที่สุด แล้ว nginx ก็ตรวจ regex ต่อ ทางแก้คือใส่ ^~ ให้ prefix ที่ยาวกว่านั้นด้วย
ทำไม request ไปตกที่ location ~ \.php$ ทั้งที่ URL ไม่มี .php?
try_files ที่มี URI เป็นพารามิเตอร์ตัวสุดท้าย หรือ index ที่พบไฟล์ index ทำให้เกิด internal redirect nginx เปลี่ยน URI เป็นค่าอย่าง /index.php แล้วเลือก location ใหม่ตั้งแต่ต้น regex \.php$ จึงตรง ถ้าต้องการให้ fallback ไปที่บล็อกที่กำหนดเอง ใช้ named location เช่น try_files $uri $uri/ @fallback; ซึ่งไม่เลือก location ใหม่และไม่เปลี่ยน URI
location /app กับ location /app/ ต่างกันอย่างไร?
location /app เทียบทีละตัวอักษร จึงตรงกับ /app, /app/x และ /application ด้วย ส่วน location /app/ ตรงเฉพาะ URI ที่ขึ้นต้นด้วย /app/ ถ้าต้องการรับ /app ที่ไม่มี / ท้ายด้วย ให้เพิ่ม location = /app { return 301 /app/; }
แก้ location แล้ว แต่ผลไม่เปลี่ยน เกิดจากอะไร?
สาเหตุแรกที่ควรตรวจคือ reload ไม่สำเร็จ ถ้า config มีข้อผิดพลาด nginx ที่รันอยู่จะใช้ config เดิมต่อไป รัน sudo nginx -t ก่อนทุกครั้ง แล้วค่อย sudo systemctl reload nginx สาเหตุที่สองคือ request ไปตก server block อื่น เพราะ server_name หรือ listen ไม่ตรง ดู config ที่โหลดจริงด้วย sudo nginx -T แล้วทดสอบจากตัว VPS ด้วย curl -sI -H 'Host: โดเมนของคุณ' http://127.0.0.1/path