رفتن به محتوا
LoopX

location و مسیرها

توی این درس یاد می‌گیری داخل یک سایت، برای مسیرهای مختلف رفتار جدا تعریف کنی: /api/ به اپلیکیشن برود، /static/ مستقیم از دیسک با کش طولانی، عکس‌ها با یک قانون و بقیه با قانونی دیگر. ابزارش بلوک location است. چهار نوعش را می‌شناسی (پیشوندی، دقیق =، ^~ و regex با ~ و ~*) و مهم‌تر از همه، ترتیب دقیق انتخاب بینشان را؛ که منبع بیشتر سردرگمی‌هاست. با try_files می‌گویی «اول این فایل را امتحان کن، نبود آن یکی، نبود فلان کار»، و فرق root و alias را با یک باگ امنیتی واقعی می‌بینی.

مسئله: یک سایت، چند جور مسیر

Section titled “مسئله: یک سایت، چند جور مسیر”

سایت واقعی فقط «یک پوشه فایل» نیست. یک فروشگاه را در نظر بگیر:

  • / و صفحه‌های فروشگاه ← اپلیکیشن (یا فایل‌های build شده‌ی یک SPA)
  • /api/... ← سرور API، با timeout و هدرهای خودش
  • /static/... ← CSS و JS، مستقیم از دیسک، با کش یک‌ساله
  • /uploads/... ← عکس‌های کاربران، از پوشه‌ای دیگر روی دیسک، بدون اجازه‌ی اجرای هیچ اسکریپتی
  • /admin ← فقط از IP دفتر

همه زیر یک دامنه و یک server block. Nginx باید برای هر آدرس بداند کدام قانون اعمال شود.

تشبیه: میز پذیرش و برگه‌ی مسیریابی

Section titled “تشبیه: میز پذیرش و برگه‌ی مسیریابی”

اگر server block پذیرش یک ساختمان باشد (درس قبل)، locationها برگه‌ی مسیریابی روی میز پذیرش‌اند: «هر کس دنبال اتاق ۱۰۵ است، مستقیم برود آنجا» (تطبیق دقیق =)؛ «هر کس دنبال طبقه‌ی سوم است، آسانسور سمت راست» (پیشوندی)؛ «هر کس بسته‌ی پستی دارد، از هر طبقه‌ای، برود انبار» (regex روی پسوند). مشکل وقتی است که دو قانون با هم جور شوند؛ آن‌وقت باید بدانی پذیرش کدام را اول نگاه می‌کند.

شکل اسم جور می‌شود با نمونه
location = /path دقیق فقط دقیقاً همان مسیر location = /health
location ^~ /path/ پیشوندی با اولویت هر مسیری که با آن شروع شود، و جلوی بررسی regex را می‌گیرد location ^~ /static/
location ~ regex regex (حساس به حروف) هر مسیری که regex با آن جور شود location ~ \.php$
location ~* regex regex (بدون حساسیت به حروف) همان، JPG و jpg یکی location ~* \.(jpg|png)$
location /path پیشوندی معمولی هر مسیری که با آن شروع شود location /api/
ترتیب انتخاب location: اول تطبیق دقیق (=)؛ اگر نبود، بلندترین پیشوند جور شده پیدا و «به خاطر سپرده» می‌شود؛ اگر آن پیشوند ^~ داشت، همان برنده است؛ وگرنه regex ها به ترتیب نوشتن بررسی می‌شوند و اولین regex جور، برنده است؛ اگر هیچ regex ای جور نشد، همان بلندترین پیشوند.

این درس روی ماشین آزمایشی server (Ubuntu 24.04، Nginx 1.24) با کاربر root اجرا شده. برای اینکه ببینیم کدام location جواب داده، اول بیشتر مثال‌ها به‌جای فایل واقعی، با return 200 "اسم location" جواب می‌دهند.

یک تابع کوچک برای آزمایش چند آدرس پشت هم (t کد و بدنه‌ی پاسخ را در یک خط چاپ می‌کند):

cat > /usr/local/bin/t <<'EOF'
#!/usr/bin/env bash
# t URI...: print status code and body (first line) for each URI on shop.test
for u in "$@"; do
printf '%-26s -> ' "$u"
curl -s --path-as-is -o /tmp/t.body -w '%{http_code} ' "http://shop.test$u"
head -n 1 /tmp/t.body | tr -d '\r' | cut -c1-60
done
EOF
chmod +x /usr/local/bin/t
echo "ready"
خروجی
ready

مثال ۱: location پیشوندی، بلندترین برنده است

Section titled “مثال ۱: location پیشوندی، بلندترین برنده است”
/etc/nginx/sites-available/shop.test
server {
listen 80;
server_name shop.test;
location / {
return 200 "prefix /\n";
}
location /images/ {
return 200 "prefix /images/\n";
}
location /images/products/ {
return 200 "prefix /images/products/\n";
}
}
Terminal window
ln -sf /etc/nginx/sites-available/shop.test /etc/nginx/sites-enabled/
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t / /about /images/logo.png /images/products/42.jpg /imagesX
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/ -> 200 prefix /
/about -> 200 prefix /
/images/logo.png -> 200 prefix /images/
/images/products/42.jpg -> 200 prefix /images/products/
/imagesX -> 200 prefix /
  • location / با همه چیز جور می‌شود (هر مسیری با / شروع می‌شود)؛ پس «پیش‌فرض» سایت است.
  • /images/products/42.jpg با هر سه پیشوند جور است؛ بلندترین برنده شد. ترتیب نوشتن پیشوندها مهم نیست.
  • /imagesX با /images/ جور نشد (اسلش آخر)، پس به / رسید. اگر location /images (بدون اسلش) می‌نوشتی، /imagesX و /images-old را هم می‌گرفت؛ معمولاً اسلش آخر را بنویس.

= فقط با دقیقاً همان مسیر جور می‌شود و اگر جور شد، جست‌وجو همان‌جا تمام می‌شود (سریع‌ترین نوع):

Terminal window
sed -i 's|^ location / {| location = /health {\n return 200 "exact = /health\\n";\n }\n location / {|' /etc/nginx/sites-available/shop.test
grep -A2 'location = ' /etc/nginx/sites-available/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /health /health/ /healthz '/health?full=1'
خروجی
location = /health {
return 200 "exact = /health\n";
}
nginx: configuration file /etc/nginx/nginx.conf test is successful
/health -> 200 exact = /health
/health/ -> 200 prefix /
/healthz -> 200 prefix /
/health?full=1 -> 200 exact = /health

/health و /health?full=1 با = جور شدند (query string بخشی از مسیر نیست). ولی /health/ و /healthz نه؛ به / رسیدند. = برای مسیرهای پرتکرار و ثابت عالی است: صفحه‌ی سلامت، favicon.ico، robots.txt.

regex برای قانون‌هایی است که به پسوند یا الگو مربوط‌اند، نه به پوشه. ~ حساس به حروف است و ~* نیست:

Terminal window
sed -i 's|^ location / {| location ~* \\.(jpg\|jpeg\|png\|webp)$ {\n return 200 "regex ~* image\\n";\n }\n location ~ \\.php$ {\n return 200 "regex ~ php\\n";\n }\n location / {|' /etc/nginx/sites-available/shop.test
grep -A2 'location ~' /etc/nginx/sites-available/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /photo.jpg /PHOTO.JPG /index.php /INDEX.PHP /images/logo.png /images/products/42.jpg
خروجی
location ~* \.(jpg|jpeg|png|webp)$ {
return 200 "regex ~* image\n";
}
location ~ \.php$ {
return 200 "regex ~ php\n";
}
nginx: configuration file /etc/nginx/nginx.conf test is successful
/photo.jpg -> 200 regex ~* image
/PHOTO.JPG -> 200 regex ~* image
/index.php -> 200 regex ~ php
/INDEX.PHP -> 200 prefix /
/images/logo.png -> 200 regex ~* image
/images/products/42.jpg -> 200 regex ~* image
  • ~* هم jpg و هم JPG را گرفت؛ ~ فقط .php کوچک را (INDEX.PHP به / رسید).
  • و نکته‌ی مهم: /images/logo.png و /images/products/42.jpg که قبلاً به پیشوندهای /images/... می‌رفتند، حالا به regex رفتند! regex بر پیشوند معمولی برنده است، حتی بر بلندترین آن. این همان جایی است که بیشتر سردرگمی‌ها شروع می‌شود.

مثال ۴: ^~، پیشوندی که regex را کنار می‌زند

Section titled “مثال ۴: ^~، پیشوندی که regex را کنار می‌زند”

اگر می‌خواهی یک پیشوند حتماً برنده باشد (مثلاً همه‌ی /images/products/ از یک جای خاص بیاید، چه پسوندش .jpg باشد چه نه)، ^~ بنویس:

Terminal window
sed -i 's| location /images/products/ {| location ^~ /images/products/ {|' /etc/nginx/sites-available/shop.test
grep -n 'location' /etc/nginx/sites-available/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /images/products/42.jpg /images/logo.png
خروجی
5: location = /health {
8: location ~* \.(jpg|jpeg|png|webp)$ {
11: location ~ \.php$ {
14: location / {
17: location /images/ {
20: location ^~ /images/products/ {
nginx: configuration file /etc/nginx/nginx.conf test is successful
/images/products/42.jpg -> 200 prefix /images/products/
/images/logo.png -> 200 regex ~* image

حالا /images/products/42.jpg به ^~ /images/products/ رفت (regex بررسی نشد)، ولی /images/logo.png هنوز به regex می‌رود چون بلندترین پیشوندش (/images/) ^~ ندارد.

مثال ۵: کل ترتیب، در یک جدول

Section titled “مثال ۵: کل ترتیب، در یک جدول”

فایل نهایی پنج location دارد. همه‌ی حالت‌ها را یک‌جا ببینیم:

Terminal window
t / /health /about /photo.JPG /index.php /images/logo.png /images/a.txt /images/products/42.jpg /images/products/list
خروجی
/ -> 200 prefix /
/health -> 200 exact = /health
/about -> 200 prefix /
/photo.JPG -> 200 regex ~* image
/index.php -> 200 regex ~ php
/images/logo.png -> 200 regex ~* image
/images/a.txt -> 200 prefix /images/
/images/products/42.jpg -> 200 prefix /images/products/
/images/products/list -> 200 prefix /images/products/

بازخوانی با ترتیب Nginx:

آدرس =؟ بلندترین پیشوند ^~؟ اولین regex جور برنده
/health ✅ = /health
/about / — /
/photo.JPG / ~* تصویر regex
/images/logo.png /images/ ❌ ~* تصویر regex
/images/a.txt /images/ ❌ — /images/
/images/products/42.jpg /images/products/ ✅ (بررسی نمی‌شود) ^~

مثال ۶: try_files، «اول این، نبود آن»

Section titled “مثال ۶: try_files، «اول این، نبود آن»”

try_files فهرستی از جاهاست که Nginx به ترتیب امتحان می‌کند؛ اولین موجود را سرو می‌کند و اگر هیچ‌کدام نبود، آخرین آیتم (یک کد خطا مثل =404، یا یک آدرس دیگر) اجرا می‌شود. حالا سایت را با فایل واقعی بازنویسی می‌کنیم:

Terminal window
mkdir -p /var/www/shop.test/docs
echo '<h1>home</h1>' > /var/www/shop.test/index.html
echo '<h1>about page</h1>' > /var/www/shop.test/about.html
echo '<h1>docs index</h1>' > /var/www/shop.test/docs/index.html
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
index index.html;
location / {
try_files $uri $uri.html $uri/ =404;
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t / /about.html /about /docs/ /docs /nope
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/ -> 200 <h1>home</h1>
/about.html -> 200 <h1>about page</h1>
/about -> 200 <h1>about page</h1>
/docs/ -> 200 <h1>docs index</h1>
/docs -> 301 <html>
/nope -> 404 <html>

برای /about: اول $uri یعنی فایل /about (نبود)، بعد $uri.html یعنی /about.html (بود!) و همان سرو شد؛ پس آدرس‌های «تمیز» بدون .html داری. $uri/ پوشه را امتحان می‌کند (و با index فایل داخلش). /nope با هیچ‌کدام جور نشد و به =404 رسید.

هر دو مشخص می‌کنند فایل‌ها کجا هستند، ولی متفاوت حساب می‌کنند:

  • root: مسیر کامل درخواست را به انتهای پوشه اضافه می‌کند. location /static/ { root /srv; } و درخواست /static/app.css ← /srv/static/app.css.
  • alias: بخشی از مسیر را که با location جور شده جایگزین می‌کند. location /static/ { alias /srv/assets/; } و درخواست /static/app.css ← /srv/assets/app.css.
Terminal window
mkdir -p /srv/assets /srv/static
echo 'from /srv/assets' > /srv/assets/app.css
echo 'from /srv/static' > /srv/static/app.css
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
location /static/ {
root /srv;
}
location /assets-by-alias/ {
alias /srv/assets/;
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /static/app.css /assets-by-alias/app.css
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/static/app.css -> 200 from /srv/static
/assets-by-alias/app.css -> 200 from /srv/assets

قاعده‌ی سرانگشتی: اگر اسم پوشه روی دیسک با اسم مسیر یکی است، root (ساده‌تر)؛ اگر فرق دارد، alias (و آن‌وقت اسلش‌ها را دقیق بنویس؛ اشتباهات رایج).

پشت پرده: $uri، نرمال‌سازی و redirect داخلی

Section titled “پشت پرده: $uri، نرمال‌سازی و redirect داخلی”

Nginx قبل از تطبیق location، مسیر را نرمال می‌کند: %XX ها را باز می‌کند، اسلش‌های تکراری را یکی و .. را حل می‌کند. نتیجه در متغیر $uri است (و مسیر خام در $request_uri). برای همین با ../ نمی‌شود از یک location فرار کرد:

Terminal window
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
location / {
return 200 "uri=$uri request_uri=$request_uri\n";
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t '/a//b///c' '/a/b/../c' '/caf%C3%A9' '/x/./y?q=1'
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/a//b///c -> 200 uri=/a/b/c request_uri=/a//b///c
/a/b/../c -> 200 uri=/a/c request_uri=/a/b/../c
/caf%C3%A9 -> 200 uri=/café request_uri=/caf%C3%A9
/x/./y?q=1 -> 200 uri=/x/y request_uri=/x/./y?q=1

curl --path-as-is (داخل t) مسیر را دست‌نخورده فرستاد و Nginx خودش نرمالش کرد: /a//b///c ← /a/b/c، /a/b/../c ← /a/c، %C3%A9 ← é.

دومین رفتار پنهان، redirect داخلی (internal redirect) است. وقتی Nginx برای پوشه‌ی / فایل index.html را پیدا می‌کند، درخواست را داخلی به /index.html تبدیل می‌کند و دوباره از اول location ها را می‌گردد. این می‌تواند غافلگیرت کند:

Terminal window
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
index index.html;
location = / {
add_header X-Matched "exact /" always;
}
location ~ \.html$ {
add_header X-Matched "regex html" always;
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
curl -s -i http://shop.test/ | grep -iE '^x-matched|<h1>'
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
X-Matched: regex html
<h1>home</h1>

درخواست / اول با location = / جور شد، ولی چون آن location فقط پوشه را دارد، ماژول index آن را به /index.html تبدیل کرد که دوباره گشته شد و این بار با regex html جور شد؛ هدر نهایی از location دوم آمد. try_files با آیتم آخری که یک آدرس است (مثل /index.html) و error_page هم همین redirect داخلی را دارند.

ترتیب انتخاب (به خاطر بسپار):

قدم چه می‌کند
۱ اگر location = ... دقیقاً جور شد ← برنده، تمام
۲ بلندترین location پیشوندی (معمولی یا ^~) جور شده را پیدا و به خاطر بسپار
۳ اگر آن پیشوند ^~ داشت ← برنده، تمام
۴ regex ها (~ و ~*) را به ترتیب نوشتن امتحان کن؛ اولین جور ← برنده
۵ هیچ regex جور نشد ← همان پیشوند قدم ۲

شکل‌های try_files:

نوشتن کاربرد
try_files $uri $uri/ =404; سایت استاتیک ساده
try_files $uri $uri.html $uri/ =404; آدرس‌های بدون .html
try_files $uri /index.html; SPA (هر مسیری که فایل نیست به اپ برسد؛ درس بعد)
try_files $uri @app; اگر فایل نبود، به named location (مثلاً proxy به اپ)

root و alias:

root /srv; alias /srv/assets/;
در location /static/ درخواست /static/a.css /srv/static/a.css /srv/assets/a.css
کجا مجاز http، server، location فقط location
ریسک کم اسلش‌های ناهمخوان (باگ امنیتی)

۱) alias با اسلش ناهمخوان (باگ امنیتی «off-by-slash»)

Section titled “۱) alias با اسلش ناهمخوان (باگ امنیتی «off-by-slash»)”

این یکی از معروف‌ترین باگ‌های تنظیمات Nginx است. location بدون اسلش آخر و alias با اسلش:

Terminal window
mkdir -p /srv/uploads && echo 'user photo' > /srv/uploads/pic.txt
echo 'DB_PASSWORD=secret' > /srv/config.env
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
location /uploads {
alias /srv/uploads/;
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /uploads/pic.txt /uploads../config.env
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/uploads/pic.txt -> 200 user photo
/uploads../config.env -> 200 DB_PASSWORD=secret

/uploads../config.env با پیشوند /uploads جور است؛ Nginx /uploads را با /srv/uploads/ جایگزین کرد و نتیجه شد /srv/uploads/../config.env یعنی /srv/config.env: فایل رمز از بیرون پوشه‌ی آپلود خوانده شد! راه‌حل: اسلش آخر location و alias را هماهنگ کن (هر دو با اسلش: location /uploads/ { alias /srv/uploads/; })، یا اگر اسم‌ها یکی‌اند، root بنویس.

Terminal window
sed -i 's|location /uploads {|location /uploads/ {|' /etc/nginx/sites-available/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /uploads/pic.txt /uploads../config.env
rm /srv/config.env
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/uploads/pic.txt -> 200 user photo
/uploads../config.env -> 404 <html>

حالا /uploads../ با /uploads/ جور نمی‌شود و به هیچ location ای نمی‌رسد (و چون root پیش‌فرض فایلی ندارد، 404).

۲) regex ای که پیشوند را می‌خورد

Section titled “۲) regex ای که پیشوند را می‌خورد”

مثال ۳: یک location ~* \.(png|jpg)$ باعث شد عکس‌های /images/ دیگر از location /images/ سرو نشوند. راه‌حل: اگر پیشوندی باید برنده باشد، ^~.

Terminal window
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
location ~ \.(js|css)$ {
return 200 "generic static regex\n";
}
location ~ ^/admin/.*\.js$ {
return 200 "admin js regex\n";
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /admin/app.js
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/admin/app.js -> 200 generic static regex

بین regex ها اولین جور برنده است، نه «دقیق‌ترین». قانون admin هیچ‌وقت اجرا نمی‌شود. راه‌حل: regex های خاص‌تر را بالاتر بنویس، یا برای پوشه‌ها ^~ به کار ببر.

location /api با /api/users جور است، ولی با /apiary و /api-docs هم. راه‌حل: location /api/ (و اگر /api بدون اسلش هم باید کار کند، یک location = /api { return 301 /api/; }).

۵) try_files بدون آیتم آخر مناسب

Section titled “۵) try_files بدون آیتم آخر مناسب”

try_files $uri $uri/; بدون =404 یا آدرس جایگزین، اگر هیچ‌کدام نبود، آخرین آیتم ($uri/) را به‌عنوان redirect داخلی امتحان می‌کند و ممکن است به پاسخ عجیب (مثل ۵۰۰ یا ۳۰۱ بی‌دلیل) برسی. راه‌حل: همیشه آخرین آیتم یک کد (=404) یا یک آدرس/named location مشخص باشد.

✎ تمرینآسان

برای سایت shop.test یک location = /robots.txt بساز که بدون هیچ فایلی، متن User-agent: * و Disallow: /admin/ را برگرداند، و یک location = /favicon.ico که با کد 204 (بدون محتوا) جواب دهد و در لاگ دسترسی ثبت نشود (access_log off;). با curl -i آزمایش کن.

دیدن جواب
Terminal window
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nDisallow: /admin/\n";
}
location = /favicon.ico {
access_log off;
return 204;
}
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
curl -s -i http://shop.test/robots.txt | grep -vE '^(Date|Server|Connection):'
echo "==="
curl -s -i http://shop.test/favicon.ico | head -n 1
grep -c 'favicon' /var/log/nginx/access.log
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 32
User-agent: *
Disallow: /admin/
===
HTTP/1.1 204 No Content
0

default_type text/plain نوع پاسخ را تعیین کرد (return با متن، به‌صورت پیش‌فرض نوع application/octet-stream می‌گیرد). access_log off باعث شد درخواست‌های favicon در لاگ نیایند (شمارش ۰).

✎ تمرینمتوسط

تمرین اصلی درس: برای shop.test سه رفتار جدا بساز: /api/ به یک سرور پایتون روی 127.0.0.1:9101 برود (reverse proxy)، /static/ از پوشه‌ی /srv/assets/ با alias سرو شود، و بقیه‌ی مسیرها از /var/www/shop.test با try_files (و 404 اگر فایل نبود). ثابت کن هر کدام از جای درست جواب می‌دهد.

دیدن جواب
Terminal window
mkdir -p /srv/lx-api/api
echo '{"products": 3}' > /srv/lx-api/api/products
(cd /srv/lx-api && exec python3 -m http.server 9101 --bind 127.0.0.1) > /dev/null 2>&1 &
echo 'body { color: teal; }' > /srv/assets/app.css
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:9101;
}
location /static/ {
alias /srv/assets/;
}
location / {
try_files $uri $uri/ =404;
}
}
EOF
sleep 1
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t /api/products /static/app.css / /about.html /missing
curl -s -I http://shop.test/api/products | grep -i '^content-type'
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/api/products -> 200 {"products": 3}
/static/app.css -> 200 body { color: teal; }
/ -> 200 <h1>home</h1>
/about.html -> 200 <h1>about page</h1>
/missing -> 404 <html>
Content-Type: application/octet-stream

/api/products از پایتون آمد (پایتون فایل /srv/lx-api/api/products را فرستاد؛ چون proxy_pass بدون مسیر، کل آدرس را همان‌طور می‌فرستد)، /static/app.css با alias از /srv/assets/، و بقیه از root سایت. جزئیات proxy_pass (اسلش آخر، هدرها) در درس reverse proxy.

✎ تمرینسخت

پیش‌بینی کن، بعد آزمایش کن. با این تنظیمات، هر یک از آدرس‌های زیر به کدام location می‌رسد؟ اول جواب را روی کاغذ بنویس و بعد با t درستی‌اش را بسنج: /، /docs، /docs/intro، /docs/img/a.PNG، /media/v.mp4، /media/a.png.

تنظیمات
location = /docs { return 200 "A\n"; }
location /docs/ { return 200 "B\n"; }
location ^~ /media/ { return 200 "C\n"; }
location ~* \.(png|jpg)$ { return 200 "D\n"; }
location / { return 200 "E\n"; }
دیدن جواب
Terminal window
cat > /etc/nginx/sites-available/shop.test <<'EOF'
server {
listen 80;
server_name shop.test;
location = /docs { return 200 "A\n"; }
location /docs/ { return 200 "B\n"; }
location ^~ /media/ { return 200 "C\n"; }
location ~* \.(png|jpg)$ { return 200 "D\n"; }
location / { return 200 "E\n"; }
}
EOF
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
t / /docs /docs/intro /docs/img/a.PNG /media/v.mp4 /media/a.png
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
/ -> 200 E
/docs -> 200 A
/docs/intro -> 200 B
/docs/img/a.PNG -> 200 D
/media/v.mp4 -> 200 C
/media/a.png -> 200 C

/ ← E (فقط پیشوند /). /docs ← A (دقیق). /docs/intro ← B (بلندترین پیشوند، هیچ regex جور نیست). /docs/img/a.PNG ← D (پیشوند /docs/ بدون ^~ است، پس regex بررسی شد و ~* حروف بزرگ را هم گرفت). /media/v.mp4 ← C. /media/a.png ← C (چون ^~ جلوی بررسی regex را گرفت). اگر همه را درست حدس زدی، ترتیب location ها را بلدی.

⚡ بررسی سریع

هم location /images/ هست و هم location ~* \.png$. درخواست /images/logo.png به کدام می‌رسد؟

؟ آزمونک
  1. location = /health با کدام درخواست جور می‌شود؟

  2. دو regex داری: اولی ~ \.js$ و دومی ~ ^/admin/.*\.js$. درخواست /admin/app.js به کدام می‌رسد؟

  3. در location /static/ { root /srv/web; } درخواست /static/a.css کدام فایل را می‌خواند؟

  4. location /files { alias /srv/files/; } چه خطری دارد؟

  5. try_files $uri $uri.html $uri/ =404; برای درخواست /about وقتی about.html وجود دارد چه می‌کند؟

  6. چرا با location ^~ /media/ درخواست /media/a.png به regex تصویرها نمی‌رسد؟

  • location رفتار هر مسیر را داخل یک server block تعیین می‌کند. انواع: = (دقیق)، ^~ (پیشوندی با اولویت)، ~/~* (regex حساس/غیرحساس به حروف)، و پیشوندی معمولی.
  • ترتیب: = ← بلندترین پیشوند (به خاطر سپرده) ← اگر ^~ بود تمام ← regex ها به ترتیب نوشتن ← وگرنه همان پیشوند. regex بر پیشوند معمولی برنده است.
  • location /x/ (با اسلش) دقیق‌تر از /x است؛ location = /path برای مسیرهای ثابت پرتکرار.
  • try_files: جاها را به ترتیب امتحان می‌کند؛ آخرین آیتم یک کد (=404) یا آدرس/@named باشد.
  • root مسیر کامل را اضافه می‌کند؛ alias بخش جورشده را جایگزین می‌کند؛ اسلش‌ها را هماهنگ کن (باگ off-by-slash).
  • Nginx مسیر را نرمال می‌کند ($uri)؛ index و try_files و error_page باعث redirect داخلی و جست‌وجوی دوباره‌ی location ها می‌شوند.
  • برای دیباگ: return 200 "اسم location" یا add_header X-Matched ....
برگه‌ی تقلب این درس
دستورکاری که می‌کند
location = /health { return 200 "ok\n"; }تطبیق دقیق
location /api/ { proxy_pass http://127.0.0.1:3000; }پیشوندی: همه‌ی /api/...
location ^~ /static/ { ... }پیشوندی که regex را کنار می‌زند
location ~* \.(jpg|png|webp)$ { ... }regex بدون حساسیت به حروف
try_files $uri $uri/ =404;سایت استاتیک
try_files $uri $uri.html $uri/ =404;آدرس بدون .html
try_files $uri @app;نبود؟ برو به named location
location /static/ { root /srv; }/static/a ← /srv/static/a
location /static/ { alias /srv/assets/; }/static/a ← /srv/assets/a (اسلش‌ها هماهنگ)
add_header X-Matched "name" always;کدام location جواب داد؟
curl --path-as-is "http://site/a/../b"فرستادن مسیر بدون نرمال‌سازی curl