توی این درس یاد میگیری چند سایت با دامنههای مختلف را روی یک سرور و یک IP میزبانی کنی. هر سایت یک server block است با چهار directive اصلی: listen (روی کدام پورت و IP)، server_name (برای کدام دامنهها)، root (فایلها کجا) و index (کدام فایل برای پوشه). میفهمی Nginx چطور با هدر Host تصمیم میگیرد کدام سایت جواب بدهد، سرور پیشفرض (default_server) کیست و چرا باید خودت تعیینش کنی، و با /etc/hosts دو دامنهی محلی میسازی تا همه را بدون DNS واقعی آزمایش کنی.
مسئله: یک سرور، چند دامنه
Section titled “مسئله: یک سرور، چند دامنه”یک سرور ابری با یک IP خریدهای. میخواهی shop.example.com، blog.example.com و example.com را روی همین سرور داشته باشی. DNS هر سه را به همان IP میفرستد و هر سه روی همان پورت ۸۰ میآیند. پس Nginx از کجا بفهمد هر درخواست مال کدام سایت است؟ جواب در خودِ درخواست است: مرورگر در هر درخواست HTTP یک هدر Host میفرستد که اسم دامنه در آن است. به این روش میزبانی مجازی بر اساس نام (name-based virtual hosting) میگویند و تقریباً همهی وب امروز همینطور کار میکند.
تشبیه: یک ساختمان، چند شرکت
Section titled “تشبیه: یک ساختمان، چند شرکت”سرور مثل یک ساختمان اداری با یک آدرس پستی (IP) است که چند شرکت در آن هستند. همهی نامهها به همان آدرس میآیند و روی پاکت اسم شرکت نوشته شده (هدر Host). نگهبان (Nginx) پاکت را نگاه میکند و به طبقهی همان شرکت میفرستد (server_name). اگر اسم روی پاکت مال هیچکدام نباشد، نامه به میز پیشفرض میرود (default_server)؛ و عاقلانه است آن میز یک سطل باشد، نه دفتر مدیرعامل یکی از شرکتها.
اجزای یک server block
Section titled “اجزای یک server block”server { listen 80; # port (and optionally IP) to accept on server_name shop.test www.shop.test; # which Host names this block serves root /var/www/shop.test; # where files live index index.html; # file to show for a directory (/)}| directive | کار | پیشفرض |
|---|---|---|
listen |
پورت و IP؛ اختیاری default_server، ssl، http2 |
*:80 |
server_name |
یک یا چند اسم: دقیق، *. wildcard، ~ regex |
"" (خالی) |
root |
پوشهی فایلها؛ آدرس به آن اضافه میشود | html (نسبت به prefix نصب) |
index |
فایل(های) پیشفرض برای آدرسهایی که به / ختم میشوند |
index.html |
مثالهای عملی
Section titled “مثالهای عملی”این درس روی ماشین آزمایشی server (Ubuntu 24.04، Nginx 1.24) با کاربر root اجرا شده؛ روی سرور خودت جلوی دستورها sudo بگذار. سایت پیشفرض Ubuntu را در این درس خاموش میکنیم تا رفتار پیشفرض را خودمان ببینیم و بسازیم.
مثال ۱: دو دامنهی محلی در /etc/hosts
Section titled “مثال ۱: دو دامنهی محلی در /etc/hosts”برای آزمایش، به دامنهی واقعی و DNS نیاز نداریم. فایل /etc/hosts نگاشت «اسم ← IP» را روی همین ماشین تعریف میکند و قبل از DNS خوانده میشود. دو دامنهی shop.test و blog.test را به 127.0.0.1 میفرستیم:
printf '127.0.0.1 shop.test www.shop.test\n127.0.0.1 blog.test\n' >> /etc/hoststail -n 2 /etc/hostsgetent hosts shop.test blog.test127.0.0.1 shop.test www.shop.test127.0.0.1 blog.test127.0.0.1 shop.test www.shop.test127.0.0.1 blog.testgetent hosts همان چیزی را نشان میدهد که برنامهها (مثل curl و مرورگر) موقع پیدا کردن IP میبینند. (روی لپتاپ خودت، برای دیدن سایتهای سرور در مرورگر، همین خطها را با IP سرور به /etc/hosts لپتاپ اضافه کن؛ در ویندوز فایل C:\Windows\System32\drivers\etc\hosts.)
مثال ۲: دو سایت، یک پورت
Section titled “مثال ۲: دو سایت، یک پورت”برای هر سایت پوشه و فایل تنظیمات جدا:
for site in shop.test blog.test; do mkdir -p /var/www/$site echo "<h1>Welcome to $site</h1>" > /var/www/$site/index.html cat > /etc/nginx/sites-available/$site <<EOFserver { listen 80; server_name $site; root /var/www/$site; index index.html;}EOF ln -s /etc/nginx/sites-available/$site /etc/nginx/sites-enabled/donecat /etc/nginx/sites-available/shop.testnginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1curl -s http://shop.test/curl -s http://blog.test/server { listen 80; server_name shop.test; root /var/www/shop.test; index index.html;}nginx: configuration file /etc/nginx/nginx.conf test is successful<h1>Welcome to shop.test</h1><h1>Welcome to blog.test</h1>هر دو دامنه به 127.0.0.1:80 رفتند، ولی هر کدام صفحهی خودش را گرفت. (حلقهی بالا با here-doc بدون کوتیشن، $site را در هر فایل باز کرد؛ درس here-doc در دورهی Bash.)
مثال ۳: هدر Host تصمیم میگیرد
Section titled “مثال ۳: هدر Host تصمیم میگیرد”ثابت کنیم تصمیم فقط از هدر Host است، نه از اسمی که curl به آن وصل شد. با -H "Host: ..." هدر را دستی عوض میکنیم و هر دو بار به IP وصل میشویم:
curl -s -H "Host: shop.test" http://127.0.0.1/curl -s -H "Host: blog.test" http://127.0.0.1/echo "--- با -v میبینیم curl چه هدری فرستاده:"curl -s -v http://blog.test/ 2>&1 | grep -E '^> (GET|Host)'<h1>Welcome to shop.test</h1><h1>Welcome to blog.test</h1>--- با -v میبینیم curl چه هدری فرستاده:> GET / HTTP/1.1> Host: blog.testاتصال هر دو بار به یک IP و پورت بود؛ فقط هدر Host فرق داشت. خطهای > در خروجی -v همان درخواستیاند که curl فرستاده: GET / HTTP/1.1 و Host: blog.test.
مثال ۴: سرور پیشفرض (default_server)
Section titled “مثال ۴: سرور پیشفرض (default_server)”حالا درخواستی با دامنهای که هیچ server_nameی ندارد. فعلاً سایت default Ubuntu با default_server روشن است:
grep -n 'default_server\|server_name' /etc/nginx/sites-enabled/default | grep -v '#'curl -s -H "Host: unknown.example" http://127.0.0.1/ | grep -o '<title>.*</title>'22: listen 80 default_server;46: server_name _;<title>Welcome to nginx!</title>دامنهی ناشناخته به سایتی رسید که listen 80 default_server دارد. حالا ببینیم اگر هیچ default_serverی نباشد چه میشود؛ سایت پیشفرض Ubuntu را خاموش میکنیم:
rm /etc/nginx/sites-enabled/defaultnginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1echo "دامنهی ناشناخته:"curl -s -H "Host: unknown.example" http://127.0.0.1/echo "IP خالی (بدون دامنه):"curl -s http://127.0.0.1/nginx -T 2>/dev/null | grep '^# configuration file /etc/nginx/sites'nginx: configuration file /etc/nginx/nginx.conf test is successfulدامنهی ناشناخته:<h1>Welcome to blog.test</h1>IP خالی (بدون دامنه):<h1>Welcome to blog.test</h1># configuration file /etc/nginx/sites-enabled/blog.test:# configuration file /etc/nginx/sites-enabled/shop.test:بدون default_server، Nginx اولین server block آن پورت را (به ترتیبی که خوانده شده؛ اینجا blog.test چون از نظر الفبا قبل از shop.test است) پیشفرض میداند. یعنی هر کس IP سرور را بزند یا دامنهی عجیبی به IP تو اشاره دهد، وبلاگ را میبیند؛ همان مشکل سناریوی اول درس. این رفتار به ترتیب فایلها بستگی دارد و با اضافه شدن یک سایت تازه بیصدا عوض میشود.
راه حرفهای: یک server block «همهگیر (catch-all)» که صریح پیشفرض باشد و درخواستهای ناشناخته را رد کند. کد غیراستاندارد 444 در Nginx یعنی «اتصال را بدون هیچ پاسخی ببند»:
server { listen 80 default_server; server_name _; return 444;}ln -s /etc/nginx/sites-available/00-default /etc/nginx/sites-enabled/nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1echo "دامنهی ناشناخته:"curl -s -H "Host: unknown.example" http://127.0.0.1/; echo "curl exit code: $?"echo "دامنههای خودمان:"curl -s http://shop.test/curl -s http://blog.test/nginx: configuration file /etc/nginx/nginx.conf test is successfulدامنهی ناشناخته:curl exit code: 52دامنههای خودمان:<h1>Welcome to shop.test</h1><h1>Welcome to blog.test</h1>curl با کد ۵۲ (Empty reply from server) تمام شد؛ Nginx اتصال را بدون پاسخ بست. ولی دو سایت واقعی عادی کار میکنند. server_name _; یک اسم بیمعنی است که با هیچ دامنهی واقعی جور نمیشود (قرارداد رایج)؛ چیزی که این block را پیشفرض میکند فقط default_server است، نه _.
مثال ۵: شکلهای server_name و ریدایرکت www
Section titled “مثال ۵: شکلهای server_name و ریدایرکت www”server_name چند شکل دارد. یک block با wildcard برای همهی زیردامنههای shop.test، و ریدایرکت www.shop.test به نسخهی بدون www:
server { listen 80; server_name www.shop.test; return 301 http://shop.test$request_uri;}
server { listen 80; server_name *.shop.test; return 200 "wildcard block: you asked for $host\n";}
server { listen 80; server_name ~^(?<user>[a-z]+)\.blog\.test$; return 200 "regex block: blog of $user\n";}ln -s /etc/nginx/sites-available/lx-names /etc/nginx/sites-enabled/nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1for h in www.shop.test api.shop.test img.shop.test sara.blog.test shop.test; do printf '%-16s -> ' "$h" curl -s -H "Host: $h" http://127.0.0.1/some/page?x=1 -o /tmp/body -w '%{http_code} %{redirect_url} ' head -c 60 /tmp/body | tr -d '\r\n' echodonenginx: configuration file /etc/nginx/nginx.conf test is successfulwww.shop.test -> 301 http://shop.test/some/page?x=1 <html><head><title>301 Moved Permanently</title></head><api.shop.test -> 200 wildcard block: you asked for api.shop.testimg.shop.test -> 200 wildcard block: you asked for img.shop.testsara.blog.test -> 200 regex block: blog of sarashop.test -> 404 <html><head><title>404 Not Found</title></head><body><www.shop.testبا اسم دقیق جور شد (اولویت اول، حتی با اینکه*.shop.testهم جورش میکرد) و با301بهhttp://shop.test/some/page?x=1فرستاده شد.$request_uriمسیر و query string اصلی را نگه میدارد.api.shop.testوimg.shop.testبا wildcard*.shop.testجور شدند؛$hostاسمی است که کاربر خواسته.sara.blog.testبا regex (با~شروع میشود) جور شد و بخش(?<user>...)در متغیر$userذخیره شد.shop.testخودش با*.shop.testجور نمیشود (wildcard فقط زیردامنهها را میگیرد) و به block خودش رسید؛404به این خاطر است که فایل/some/pageدر آن سایت وجود ندارد.
مثال ۶: listen با IP و پورتهای مختلف
Section titled “مثال ۶: listen با IP و پورتهای مختلف”listen فقط پورت نیست؛ میتواند IP هم داشته باشد. سایتی که فقط از داخل خود سرور (مثلاً برای ابزار مانیتورینگ) در دسترس باشد:
server { listen 127.0.0.1:8087; server_name _; return 200 "internal status page\n";}ln -s /etc/nginx/sites-available/lx-internal /etc/nginx/sites-enabled/nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1ss -tln | grep -E ':(80|8087) 'curl -s http://127.0.0.1:8087/ip=$(hostname -I | awk '{print $1}')echo "از IP شبکهی سرور ($ip):"curl -s -m 3 "http://$ip:8087/"; echo "curl exit code: $?"nginx: configuration file /etc/nginx/nginx.conf test is successfulLISTEN 0 511 0.0.0.0:80 0.0.0.0:*LISTEN 0 511 127.0.0.1:8087 0.0.0.0:*internal status pageاز IP شبکهی سرور (172.17.0.3):curl exit code: 7ss -tln نشان میدهد پورت ۸۰ روی همهی آدرسها (0.0.0.0) باز است ولی ۸۰۸۷ فقط روی 127.0.0.1. درخواست از IP شبکهی سرور با کد ۷ (Failed to connect) رد شد؛ این سایت از بیرون اصلاً دیده نمیشود، حتی بدون فایروال.
مثال ۷: root و index
Section titled “مثال ۷: root و index”Nginx برای پیدا کردن فایل، آدرس را به root اضافه میکند: درخواست /css/app.css با root /var/www/shop.test یعنی فایل /var/www/shop.test/css/app.css. اگر آدرس به / ختم شود (پوشه)، فایلهای فهرست index را به ترتیب امتحان میکند:
mkdir -p /var/www/shop.test/css /var/www/shop.test/docs /var/www/shop.test/emptyecho 'body { color: teal; }' > /var/www/shop.test/css/app.cssecho '<h1>docs home</h1>' > /var/www/shop.test/docs/home.htmlsed -i 's/index index.html;/index index.html home.html;/' /etc/nginx/sites-available/shop.testnginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1for path in /css/app.css /docs/ /docs /empty/ /nothing.html; do printf '%-14s -> ' "$path" curl -s -o /tmp/body -w '%{http_code} %{content_type} %{redirect_url}' "http://shop.test$path" echo " $(head -c 40 /tmp/body | tr -d '\r\n')"donenginx: configuration file /etc/nginx/nginx.conf test is successful/css/app.css -> 200 text/css body { color: teal; }/docs/ -> 200 text/html <h1>docs home</h1>/docs -> 301 text/html http://shop.test/docs/ <html><head><title>301 Moved Permanent/empty/ -> 403 text/html <html><head><title>403 Forbidden</titl/nothing.html -> 404 text/html <html><head><title>404 Not Found</titl/css/app.css← فایل باContent-Type: text/css(ازmime.types)./docs/← پوشه است؛index.htmlنبود، پس دومی (home.html) را داد./docs(بدون/آخر) ← Nginx فهمید پوشه است و با301به/docs/فرستاد./empty/← پوشه بدون هیچ فایل index:403 Forbidden؛ Nginx بهصورت پیشفرض فهرست فایلهای پوشه را نشان نمیدهد (درس سایت استاتیک:autoindex)./nothing.html←404.
پشت پرده: Nginx چطور اسمها را جستوجو میکند؟
Section titled “پشت پرده: Nginx چطور اسمها را جستوجو میکند؟”Nginx موقع خواندن تنظیمات، برای هر IP:پورت سه جدول درهمسازی (hash table) میسازد (اسمهای دقیق، wildcard ابتدا، wildcard انتها) و یک فهرست regex. هنگام درخواست، به ترتیب جستوجو میکند و اولین جوری را برمیدارد:
۱. اسم دقیق (shop.test)
۲. بلندترین wildcard ابتدایی (*.shop.test)
۳. بلندترین wildcard انتهایی (shop.*)
۴. اولین regex به ترتیب نوشتن در تنظیمات (~^...$)
۵. وگرنه default_server آن IP:پورت (یا اولین block)
برای اینکه ببینی کدام block جواب داده، متغیر $server_name (اولین اسم آن block) و $host را در یک هدر برگردان:
cat > /etc/nginx/snippets/lx-debug.conf <<'EOF'add_header X-Debug-Server "$server_name" always;add_header X-Debug-Host "$host" always;EOFfor f in shop.test blog.test; do sed -i 's| index | include snippets/lx-debug.conf;\n index |' /etc/nginx/sites-available/$fdonenginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1for h in shop.test SHOP.TEST blog.test.; do echo "== Host: $h" curl -s -I -H "Host: $h" http://127.0.0.1/ | grep -i '^x-debug'donenginx: configuration file /etc/nginx/nginx.conf test is successful== Host: shop.testX-Debug-Server: shop.testX-Debug-Host: shop.test== Host: SHOP.TESTX-Debug-Server: shop.testX-Debug-Host: shop.test== Host: blog.test.X-Debug-Server: blog.testX-Debug-Host: blog.testدو جزئیات جالب: نام دامنه حساس به حروف بزرگ و کوچک نیست (SHOP.TEST هم به shop.test رسید و $host آن را کوچک کرد)، و نقطهی آخر (blog.test.، که در DNS یعنی «نام کامل») هم حذف میشود.
جدولهای مرجع
Section titled “جدولهای مرجع”شکلهای listen:
| نوشتن | معنی |
|---|---|
listen 80; |
پورت ۸۰ روی همهی IPv4 ها |
listen [::]:80; |
پورت ۸۰ روی IPv6 |
listen 127.0.0.1:8087; |
فقط از داخل خود سرور |
listen 80 default_server; |
پیشفرض این پورت |
listen 443 ssl; |
HTTPS (درس HTTPS) |
شکلهای server_name (به ترتیب اولویت):
| شکل | نمونه | جور میشود با |
|---|---|---|
| دقیق | shop.test |
فقط همان |
| wildcard ابتدا | *.shop.test |
a.shop.test، a.b.shop.test (نه خود shop.test) |
| wildcard انتها | shop.* |
shop.test، shop.ir |
| regex | ~^(?<user>\w+)\.blog\.test$ |
هر چه regex بگوید (با گروه نامدار) |
| چند اسم | shop.test www.shop.test |
هر کدام |
_ یا "" |
هیچ دامنهی واقعی (برای catch-all) |
کدهای پاسخی که در این درس دیدیم:
| کد | معنی |
|---|---|
200 |
موفق |
301 |
انتقال دائمی (www ← بدون www، پوشه بدون /) |
403 |
ممنوع (پوشه بدون index، یا مجوز فایل) |
404 |
پیدا نشد |
444 |
(مخصوص Nginx) بستن اتصال بدون پاسخ |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) اعتماد به «اولین سایت» بهعنوان پیشفرض
Section titled “۱) اعتماد به «اولین سایت» بهعنوان پیشفرض”مثال ۴: بدون default_server، ترتیب الفبایی فایلها تعیین میکند کدام سایت جواب درخواستهای ناشناخته را بدهد. راهحل: یک block صریح با listen 80 default_server; return 444; (و برای HTTPS، listen 443 ssl default_server;).
۲) دو default_server روی یک پورت
Section titled “۲) دو default_server روی یک پورت”cat > /etc/nginx/conf.d/lx-second-default.conf <<'EOF'server { listen 80 default_server; return 200 "second default\n";}EOFnginx -t 2>&1 | grep emerg | sed -E 's/^[0-9/]+ [0-9:]+ //'rm /etc/nginx/conf.d/lx-second-default.conf[emerg] 18447#18447: a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/00-default:2فقط یک default_server به ازای هر IP:پورت مجاز است. سایت پیشفرض Ubuntu (default) هم default_server دارد؛ اگر catch-all خودت را میسازی، آن را خاموش کن.
۳) فایلهایی که www-data نمیتواند بخواند
Section titled “۳) فایلهایی که www-data نمیتواند بخواند”mkdir -p /srv/private/site && echo '<h1>secret</h1>' > /srv/private/site/index.htmlchmod 700 /srv/privatels -ld /srv/privatesed -i 's|root /var/www/blog.test;|root /srv/private/site;|' /etc/nginx/sites-available/blog.testnginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1curl -s -o /dev/null -w "HTTP %{http_code}\n" http://blog.test/tail -n 1 /var/log/nginx/error.log | sed -E 's/^[0-9/]+ [0-9:]+ //'sed -i 's|root /srv/private/site;|root /var/www/blog.test;|' /etc/nginx/sites-available/blog.testsystemctl reload nginxrm -rf /srv/privatedrwx------ 3 root root 4096 Oct 4 12:46 /srv/privatenginx: configuration file /etc/nginx/nginx.conf test is successfulHTTP 403[error] 18461#18461: *26 "/srv/private/site/index.html" is forbidden (13: Permission denied), client: 127.0.0.1, server: blog.test, request: "GET / HTTP/1.1", host: "blog.test"پوشهی /srv/private مجوز 700 دارد (فقط root)، و worker ها با کاربر www-data اجرا میشوند: 403 و در لاگ خطا Permission denied. همین اتفاق برای سایتی در /root یا پوشهی خانهی یک کاربر (با مجوز پیشفرض بسته) میافتد؛ کافی است یکی از پوشههای مسیر برای www-data مجوز x نداشته باشد (درس دسترسیها). راهحل: سایت را در /var/www بگذار و مالک یا مجوزش را درست کن (chmod 755 برای پوشهها، 644 برای فایلها).
۴) server_name که با /etc/hosts یا DNS نمیخواند
Section titled “۴) server_name که با /etc/hosts یا DNS نمیخواند”درخواست به سرور میرسد ولی به سایت پیشفرض میرود. اولین سؤال: هدر Host چیست؟ با curl -v ببین و با server_name مقایسه کن (www جاافتاده، دامنهی اشتباه، پورت).
۵) /etc/hosts روی ماشین اشتباه
Section titled “۵) /etc/hosts روی ماشین اشتباه”برای دیدن سایت آزمایشی در مرورگر لپتاپ، خط /etc/hosts باید روی لپتاپ باشد (با IP سرور)، نه روی سرور. /etc/hosts فقط روی همان ماشینی اثر دارد که رویش نوشته شده.
تمرین اصلی درس: دو سایت با دو دامنهی محلی در /etc/hosts راه بینداز (alpha.test و beta.test)، هر کدام با پوشه و صفحهی خودش، و با curl نشان بده هر دامنه صفحهی خودش را میگیرد. دامنهی سوم (gamma.test) را هم در /etc/hosts بگذار ولی سایتی برایش نساز و ببین چه اتفاقی میافتد.
دیدن جواب
printf '127.0.0.1 alpha.test beta.test gamma.test\n' >> /etc/hostsfor s in alpha beta; do mkdir -p /var/www/$s.test echo "<h1>$s</h1>" > /var/www/$s.test/index.html printf 'server {\n listen 80;\n server_name %s.test;\n root /var/www/%s.test;\n}\n' $s $s > /etc/nginx/sites-available/$s.test ln -s /etc/nginx/sites-available/$s.test /etc/nginx/sites-enabled/donenginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1curl -s http://alpha.test/curl -s http://beta.test/curl -s http://gamma.test/; echo "gamma.test -> curl exit code $?"nginx: configuration file /etc/nginx/nginx.conf test is successful<h1>alpha</h1><h1>beta</h1>gamma.test -> curl exit code 52gamma.test به catch-all 00-default رسید که اتصال را بست (curl کد ۵۲). اگر catch-all نداشتی، یکی از سایتها را نشان میداد. index را ننوشتیم چون پیشفرضش index.html است.
برای alpha.test نسخهی www.alpha.test را هم بپذیر ولی همیشه با 301 به نسخهی بدون www (با حفظ مسیر و query string) بفرست. با curl -I ثابت کن هدر Location درست است و با curl -L ثابت کن در نهایت به صفحهی درست میرسی.
دیدن جواب
echo '127.0.0.1 www.alpha.test' >> /etc/hostscat > /etc/nginx/sites-available/www.alpha.test <<'EOF'server { listen 80; server_name www.alpha.test; return 301 http://alpha.test$request_uri;}EOFln -s /etc/nginx/sites-available/www.alpha.test /etc/nginx/sites-enabled/nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1curl -s -I 'http://www.alpha.test/products?page=2' | grep -iE '^(HTTP|location)'curl -s -L 'http://www.alpha.test/'nginx: configuration file /etc/nginx/nginx.conf test is successfulHTTP/1.1 301 Moved PermanentlyLocation: http://alpha.test/products?page=2<h1>alpha</h1>return 301 سریعترین شکل ریدایرکت است (بدون خواندن هیچ فایلی). $request_uri همان مسیر و query string اصلی است (/products?page=2). curl -L ریدایرکت را دنبال کرد و به صفحهی alpha رسید. (در درس HTTPS همین الگو برای http ← https به کار میرود.)
یک block بساز که هر زیردامنهی sites.test (مثل ali.sites.test، sara.sites.test) را از پوشهی همنام در /var/www/sites/ سرو کند (ali.sites.test ← /var/www/sites/ali/)، با یک regex و گروه نامدار در server_name و استفاده از آن در root. زیردامنهای که پوشه ندارد باید 404 بگیرد. با سه زیردامنه آزمایش کن.
دیدن جواب
printf '127.0.0.1 ali.sites.test sara.sites.test nobody.sites.test\n' >> /etc/hostsfor u in ali sara; do mkdir -p /var/www/sites/$u echo "<h1>home page of $u</h1>" > /var/www/sites/$u/index.htmldonecat > /etc/nginx/sites-available/sites.test <<'EOF'server { listen 80; server_name ~^(?<user>[a-z0-9-]+)\.sites\.test$; root /var/www/sites/$user;}EOFln -s /etc/nginx/sites-available/sites.test /etc/nginx/sites-enabled/nginx -t 2>&1 | tail -n 1 && systemctl reload nginxsleep 1for h in ali sara nobody; do printf '%-18s -> ' "$h.sites.test" curl -s -o /tmp/body -w '%{http_code} ' "http://$h.sites.test/" head -c 40 /tmp/body | tr -d '\r\n'; echodonenginx: configuration file /etc/nginx/nginx.conf test is successfulali.sites.test -> 200 <h1>home page of ali</h1>sara.sites.test -> 200 <h1>home page of sara</h1>nobody.sites.test -> 404 <html><head><title>404 Not Found</titlregex با ~ شروع میشود و (?<user>...) یک متغیر نامدار میسازد که در root به کار رفته. regex را محدود نوشتیم ([a-z0-9-]+ و ^...$) تا کسی نتواند با یک Host عجیب (مثلاً شامل ../) مسیر را به جای دیگری ببرد. برای nobody پوشهای نبود: 404. (این الگو برای میزبانی صفحهی شخصی هر کاربر یا پیشنمایش هر شاخهی Git کاربرد دارد.)
آزمونک
Section titled “آزمونک”سه دامنه به یک IP اشاره میکنند و هر سه روی پورت ۸۰ میآیند. Nginx از کجا میفهمد هر درخواست مال کدام سایت است؟
به این روش name-based virtual hosting میگویند؛ در HTTPS، اسم با SNI هم فرستاده میشود.
درخواستی با Host ناشناخته به سروری میرسد که هیچ default_server ای ندارد. کدام server block جواب میدهد؟
برای همین یک catch-all صریح با default_server بساز.
server_name *.shop.test با کدام Host جور نمیشود؟
wildcard ابتدایی فقط زیردامنهها را میگیرد؛ shop.test را جدا بنویس.
هر دو block دارای www.shop.test (دقیق) و *.shop.test هستند. درخواست www.shop.test به کدام میرسد؟
ترتیب: دقیق، wildcard ابتدا، wildcard انتها، regex.
return 444 در Nginx چه میکند؟
برای catch-all دامنههای ناشناخته رایج است.
سایتی با root /home/ali/site همیشه 403 میدهد و در error.log «Permission denied» هست. علت محتمل؟
سایت را در /var/www بگذار یا مجوز مسیر را درست کن.
listen 127.0.0.1:8087; یعنی چه؟
برای صفحههای داخلی و مانیتورینگ عالی است.
جمعبندی
Section titled “جمعبندی”- هر سایت یک server block است:
listen(پورت/IP)،server_name(دامنهها)،root(پوشه) وindex(فایل پیشفرض پوشه). - چند دامنه روی یک IP و پورت با هدر
Hostاز هم جدا میشوند (name-based virtual hosting). برای آزمایش:/etc/hostsیاcurl -H "Host: ..."یاcurl --resolve. - ترتیب جور شدن
server_name: دقیق ←*.x←x.*← regex (~، با گروه نامدار) ←default_server. - بدون
default_server، اولین block (به ترتیب فایلها) پیشفرض است؛ پس یک catch-all صریح بساز:listen 80 default_server; server_name _; return 444;. فقط یکdefault_serverبه ازای هر IP:پورت. - ریدایرکت
www:return 301 http://example.com$request_uri;. listen 127.0.0.1:PORTبرای صفحههای فقط-داخلی.root+ آدرس = مسیر فایل؛ پوشه بدون/←301؛ پوشه بدون index ←403؛ فایلها باید برایwww-dataخواندنی باشند.
| دستور | کاری که میکند |
|---|---|
server { listen 80; server_name shop.test; root /var/www/shop.test; index index.html; } | یک سایت |
listen 80 default_server; server_name _; return 444; | catch-all برای دامنههای ناشناخته |
server_name shop.test www.shop.test; | چند اسم برای یک سایت |
server_name *.shop.test; | همهی زیردامنهها |
server_name ~^(?<user>[a-z0-9-]+)\.sites\.test$; | regex با گروه نامدار |
return 301 http://shop.test$request_uri; | ریدایرکت با حفظ مسیر |
listen 127.0.0.1:8087; | فقط داخلی |
echo "127.0.0.1 shop.test" | sudo tee -a /etc/hosts | دامنهی محلی برای آزمایش |
curl -H "Host: shop.test" http://127.0.0.1/ | آزمایش با هدر Host دلخواه |
curl -sv http://shop.test/ 2>&1 | grep '^> Host' | دیدن هدر Host فرستادهشده |
add_header X-Debug-Server $server_name always; | کدام block جواب داد؟ |