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

ساختار فایل‌های تنظیمات

توی این درس یاد می‌گیری هر تنظیم Nginx کجا نوشته می‌شود و چرا آنجا. فایل اصلی nginx.conf را خط‌به‌خط می‌خوانی؛ دو جزء زبان تنظیمات را می‌شناسی: directive (یک دستور، مثل listen 80;) و context (یک بلوک آکولادی، مثل server { ... })؛ می‌بینی تنظیم‌ها چطور از context بیرونی به درونی ارث می‌رسند. سایت‌ها را به روش Ubuntu در sites-available می‌نویسی و با sudo ln -s در sites-enabled روشن می‌کنی، فرقش با conf.d را می‌فهمی، و هر خطایی را با sudo nginx -t تا شماره‌ی خط پیدا می‌کنی؛ بعد با sudo systemctl reload nginx اعمالش می‌کنی.

روی سروری که پنج سایت دارد، اگر همه‌ی تنظیمات در یک فایل باشند، هر تغییری ترسناک است: یک } اشتباه همه‌ی سایت‌ها را پایین می‌آورد، نمی‌دانی هر بلوک مال کدام سایت است، و برای خاموش کردن موقت یک سایت باید چندصد خط را کامنت کنی. Nginx این را با تکه‌تکه کردن تنظیمات در چند فایل و دستور include حل می‌کند، و Ubuntu یک قرارداد مرتب برایش دارد: هر سایت یک فایل، و روشن/خاموش کردن با یک لینک.

تشبیه: دفترچه‌ی قوانین یک ساختمان

Section titled “تشبیه: دفترچه‌ی قوانین یک ساختمان”

تنظیمات Nginx مثل دفترچه‌ی قوانین یک مجتمع اداری است. یک فصل کلی دارد که برای همه است («ساعت کاری ۸ تا ۱۷»)، بعد برای هر ساختمان (سایت، server) یک فصل، و داخل هر ساختمان برای طبقه‌ها و اتاق‌ها (مسیرها، location) بخش‌های جدا. هر قانون کلی برای همه‌ی ساختمان‌ها معتبر است، مگر اینکه فصل یک ساختمان آن را عوض کرده باشد (ارث‌بری). فصل‌ها هم در پوشه‌های جدا نگه داشته می‌شوند و فهرست اصلی می‌گوید «فصل‌های داخل این پوشه را هم بخوان» (include).

شکل کلی
directive value; # simple directive: name, values, semicolon
context { # block directive: name, braces
directive value;
inner_context {
directive value;
}
}
  • directive ساده: اسم، یک یا چند مقدار، و حتماً ; آخرش.
  • context (بلوک): اسم و آکولاد؛ directive های داخلش فقط در همان محدوده اثر دارند.
  • کامنت: هر چیزی بعد از # تا آخر خط.
  • هر directive فقط در context های مشخصی مجاز است (در مستندات هر directive بخش Context: همین را می‌گوید).

context های اصلی، از بیرون به درون:

context کجاست چه چیزی را تنظیم می‌کند
main (بیرون از همه‌ی بلوک‌ها) بالای nginx.conf کاربر worker ها، تعداد worker، فایل PID، لاگ خطای سراسری
events events { } اتصال‌ها: worker_connections
http http { } همه‌چیز مربوط به وب: نوع فایل‌ها، لاگ دسترسی، gzip، و همه‌ی سایت‌ها
server داخل http یک سایت (یا یک «میزبان مجازی»): listen، server_name، root
location داخل server رفتار یک مسیر (مثل /api/ یا /images/)
upstream داخل http گروه سرورهای پشتی (درس load balancing)
درخت تنظیمات Nginx در Ubuntu: فایل اصلی nginx.conf با include فایل‌های conf.d و sites-enabled را داخل context http می‌آورد؛ هر فایل در sites-enabled فقط یک لینک به فایلی در sites-available است.

این درس روی ماشین آزمایشی server (Ubuntu 24.04، Nginx 1.24 از مخزن Ubuntu) با کاربر root اجرا شده؛ روی سرور خودت جلوی دستورها sudo بگذار (همان‌طور که در جدول‌ها آمده).

Terminal window
tree -L 1 --noreport /etc/nginx
echo "--- سایت‌های موجود و فعال:"
ls -l /etc/nginx/sites-available/ /etc/nginx/sites-enabled/ | grep -v '^total'
خروجی
/etc/nginx
├── conf.d
├── fastcgi.conf
├── fastcgi_params
├── koi-utf
├── koi-win
├── mime.types
├── modules-available
├── modules-enabled
├── nginx.conf
├── proxy_params
├── scgi_params
├── sites-available
├── sites-enabled
├── snippets
├── uwsgi_params
└── win-utf
--- سایت‌های موجود و فعال:
/etc/nginx/sites-available/:
-rw-r--r-- 1 root root 2413 Oct 4 12:41 default
/etc/nginx/sites-enabled/:
lrwxrwxrwx 1 root root 34 Oct 4 12:41 default -> /etc/nginx/sites-available/default

دو پوشه‌ی کلیدی: sites-available همه‌ی سایت‌هایی که تنظیمشان را نوشته‌ای (فعال یا نه)، و sites-enabled فقط سایت‌های روشن؛ که هر کدام فقط یک لینک نمادین (symbolic link) به فایل اصلی است (default -> /etc/nginx/sites-available/default). (درس لینک‌ها در دوره‌ی لینوکس.) conf.d خالی است و snippets چند تکه‌ی آماده دارد.

فایل اصلی را بدون کامنت‌ها و خط‌های خالی ببینیم:

Terminal window
grep -nvE '^\s*(#|$)' /etc/nginx/nginx.conf
خروجی
1:user www-data;
2:worker_processes auto;
3:pid /run/nginx.pid;
4:error_log /var/log/nginx/error.log;
5:include /etc/nginx/modules-enabled/*.conf;
7:events {
8: worker_connections 768;
10:}
12:http {
18: sendfile on;
19: tcp_nopush on;
20: types_hash_max_size 2048;
26: include /etc/nginx/mime.types;
27: default_type application/octet-stream;
33: ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
34: ssl_prefer_server_ciphers on;
40: access_log /var/log/nginx/access.log;
46: gzip on;
59: include /etc/nginx/conf.d/*.conf;
60: include /etc/nginx/sites-enabled/*;
61:}

از بالا به پایین:

  • main: user www-data; (کاربر worker ها، درس قبل)، worker_processes auto;، pid /run/nginx.pid;، error_log سراسری، و include ماژول‌های پویا.
  • events { worker_connections 768; }: هر worker حداکثر ۷۶۸ اتصال همزمان.
  • http { ... }: تنظیم‌های وب که برای همه‌ی سایت‌ها اعمال می‌شوند: sendfile on; (فرستادن فایل با کمک مستقیم هسته)، include /etc/nginx/mime.types; (نگاشت پسوند به نوع فایل)، default_type برای پسوندهای ناشناخته، تنظیم‌های پایه‌ی SSL، access_log، gzip on;.
  • و دو خط مهم آخر http: include /etc/nginx/conf.d/*.conf; و include /etc/nginx/sites-enabled/*;. یعنی محتوای همه‌ی این فایل‌ها دقیقاً همین‌جا، داخل http قرار می‌گیرد. برای همین فایل‌های سایت با server { ... } شروع می‌شوند، نه با http.

مثال ۳: یک سایت تازه با sites-available و sudo ln -s

Section titled “مثال ۳: یک سایت تازه با sites-available و sudo ln -s”

سایت shop.test را می‌سازیم. (دامنه‌ی .test برای آزمایش رزرو شده و هیچ‌وقت روی اینترنت واقعی نیست.) اول فایل‌های سایت:

Terminal window
mkdir -p /var/www/shop.test
echo '<h1>shop.test</h1>' > /var/www/shop.test/index.html

بعد فایل تنظیمات در sites-available:

/etc/nginx/sites-available/shop.test
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
index index.html;
}

تا وقتی لینکش در sites-enabled نباشد، Nginx این فایل را نمی‌خواند. با ln -s روشنش می‌کنیم، آزمایش و reload:

Terminal window
ln -s /etc/nginx/sites-available/shop.test /etc/nginx/sites-enabled/
ls -l /etc/nginx/sites-enabled/ | grep shop
nginx -t
systemctl reload nginx
sleep 1
curl -s --resolve shop.test:80:127.0.0.1 http://shop.test/
خروجی
lrwxrwxrwx 1 root root 36 Oct 4 12:41 shop.test -> /etc/nginx/sites-available/shop.test
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
<h1>shop.test</h1>
  • ln -s مقصد پوشه/ یک لینک با همان اسم در sites-enabled ساخت. همیشه مسیر مطلق به فایل اصلی بده؛ لینک نسبی اشتباه، لینک شکسته می‌سازد (اشتباهات رایج).
  • curl --resolve shop.test:80:127.0.0.1 به curl می‌گوید «shop.test را 127.0.0.1 بدان»، بدون اینکه به DNS یا /etc/hosts دست بزنیم. Nginx از هدر Host فهمید درخواست مال کدام server است (درس بعد کامل).
  • بعد از reload یک sleep 1 گذاشته‌ایم: systemctl reload فقط سیگنال را به master می‌فرستد و برمی‌گردد؛ ساختن worker های تازه چند لحظه طول می‌کشد. اگر بلافاصله curl بزنی، ممکن است هنوز worker قدیمی (با تنظیمات قبلی) جواب بدهد. در کار روزمره این فاصله را حس نمی‌کنی، ولی در اسکریپت‌ها مهم است.

خاموش کردن سایت فقط برداشتن لینک است؛ فایل اصلی می‌ماند:

Terminal window
rm /etc/nginx/sites-enabled/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
curl -s --resolve shop.test:80:127.0.0.1 http://shop.test/ | grep -o '<title>.*</title>'
ls /etc/nginx/sites-available/
ln -s /etc/nginx/sites-available/shop.test /etc/nginx/sites-enabled/
systemctl reload nginx
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
<title>Welcome to nginx!</title>
default
shop.test

بعد از برداشتن لینک، درخواست shop.test به سایت پیش‌فرض رسید («Welcome to nginx»؛ درس بعد می‌گوید چرا) و فایل shop.test هنوز در sites-available است. در آخر دوباره روشنش کردیم. (دستور a2ensite که در Apache هست، در Nginx نیست؛ همین ln -s و rm کارش را می‌کنند.)

مثال ۴: conf.d، فایل‌هایی که خودشان روشن‌اند

Section titled “مثال ۴: conf.d، فایل‌هایی که خودشان روشن‌اند”

هر فایل *.conf در conf.d خودکار خوانده می‌شود؛ لینکی لازم نیست. برای خاموش کردن، باید اسمش را عوض کنی (مثلاً .conf.off) یا پاکش کنی:

/etc/nginx/conf.d/status.conf
server {
listen 8085;
location / {
return 200 "status: ok\n";
}
}
Terminal window
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
curl -s http://localhost:8085/
mv /etc/nginx/conf.d/status.conf /etc/nginx/conf.d/status.conf.off
systemctl reload nginx
sleep 1
curl -s -o /dev/null -w "after rename: HTTP %{http_code}\n" http://localhost:8085/ || true
mv /etc/nginx/conf.d/status.conf.off /etc/nginx/conf.d/status.conf
systemctl reload nginx
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
status: ok
after rename: HTTP 000

return 200 "متن"; یک پاسخ مستقیم بدون هیچ فایلی می‌دهد (برای صفحه‌ی وضعیت و آزمایش عالی است). بعد از تغییر اسم به .conf.off، الگوی *.conf دیگر آن را نگرفت و پورت ۸۰۸۵ بسته شد (000 یعنی اتصالی برقرار نشد).

کدام را به کار ببری؟ قرارداد رایج: sites-available/sites-enabled برای سایت‌ها (روشن/خاموش با لینک)، و conf.d برای تنظیم‌های سراسری http (مثل log_format یا limit_req_zone) و برای ایمیج داکر Nginx که اصلاً sites-* ندارد. هر دو داخل http خوانده می‌شوند.

مثال ۵: include و snippets برای تکه‌های تکراری

Section titled “مثال ۵: include و snippets برای تکه‌های تکراری”

تنظیمی که در چند سایت لازم است (مثلاً چند هدر امنیتی)، یک بار در snippets نوشته و هر جا لازم بود include می‌شود:

/etc/nginx/snippets/lx-headers.conf
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
Terminal window
sed -i 's|^ index index.html;| index index.html;\n include snippets/lx-headers.conf;|' /etc/nginx/sites-available/shop.test
cat /etc/nginx/sites-available/shop.test
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
curl -s -I --resolve shop.test:80:127.0.0.1 http://shop.test/ | grep -i '^x-'
خروجی
server {
listen 80;
server_name shop.test;
root /var/www/shop.test;
index index.html;
include snippets/lx-headers.conf;
}
nginx: configuration file /etc/nginx/nginx.conf test is successful
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN

مسیر نسبی در include (snippets/...) نسبت به پوشه‌ی پیکربندی (/etc/nginx) حساب می‌شود. حالا اگر سیاست هدرها عوض شود، فقط یک فایل را عوض می‌کنی. (معنی خود این هدرها در درس امنیت.)

مثال ۶: پیدا کردن خطا با nginx -t

Section titled “مثال ۶: پیدا کردن خطا با nginx -t”

nginx -t قبل از هر reload، کل تنظیمات را می‌خواند و اولین خطا را با اسم فایل و شماره‌ی خط می‌گوید. چند خطای رایج را عمداً می‌سازیم (هر بار روی یک کپی، و بعد برمی‌گردانیم):

Terminal window
f=/etc/nginx/sites-available/shop.test
cp "$f" /root/shop.test.bak
try() {
echo "=== $1"
nginx -t 2>&1 | grep -E 'emerg|warn' | sed -E 's/^[0-9/]+ [0-9:]+ //'
cp /root/shop.test.bak "$f"
}
sed -i 's/server_name shop.test;/server_name shop.test/' "$f"
try "1) جا انداختن ;"
sed -i 's/root /rooot /' "$f"
try "2) اسم directive اشتباه"
sed -i 's/^}$//' "$f"
try "3) آکولاد بسته‌نشده"
sed -i 's/listen 80;/listen 80;\n worker_connections 100;/' "$f"
try "4) directive در context اشتباه"
sed -i 's/listen 80;/listen 80;\n listen 80;/' "$f"
try "5) listen تکراری"
nginx -t 2>&1 | tail -n 1
خروجی
=== 1) جا انداختن ;
[warn] 14721#14721: server name "/var/www/shop.test" has suspicious symbols in /etc/nginx/sites-enabled/shop.test:5
=== 2) اسم directive اشتباه
[emerg] 14726#14726: unknown directive "rooot" in /etc/nginx/sites-enabled/shop.test:5
=== 3) آکولاد بسته‌نشده
[emerg] 14731#14731: unexpected end of file, expecting "}" in /etc/nginx/sites-enabled/shop.test:9
=== 4) directive در context اشتباه
[emerg] 14736#14736: "worker_connections" directive is not allowed here in /etc/nginx/sites-enabled/shop.test:3
=== 5) listen تکراری
[emerg] 14741#14741: a duplicate listen 0.0.0.0:80 in /etc/nginx/sites-enabled/shop.test:3
nginx: configuration file /etc/nginx/nginx.conf test is successful

هر پیام سه چیز دارد: سطح ([emerg] یعنی «اضطراری، شروع نمی‌کنم»)، توضیح، و in فایل:خط. چند نکته:

  • مورد ۱ از همه خطرناک‌تر است: خطا نداد، فقط یک [warn] روی خط بعدی! چون Nginx تا ; بعدی همه را جزو directive قبلی می‌داند، server_name سه مقدار گرفت: shop.test، root و /var/www/shop.test، و دیگر هیچ rootی در سایت نماند (پس پوشه‌ی پیش‌فرض سرو می‌شد). هشدار «server name has suspicious symbols» تنها سرنخ بود. پس هشدارها را بخوان، و وقتی خطایی عجیب است، خط قبل را هم نگاه کن.
  • در ۳، خطا در آخر فایل (unexpected end of file) گزارش شد؛ آکولاد بسته‌نشده تا ته فایل کشف نمی‌شود.
  • در ۴، worker_connections مال context events است و در server مجاز نیست: directive is not allowed here.
  • در ۵، listen تکراری در یک server خطای a duplicate listen داد.

پشت پرده: nginx -T و ارث‌بری

Section titled “پشت پرده: nginx -T و ارث‌بری”

nginx -T (T بزرگ) کار -t را می‌کند و بعد کل تنظیمات نهایی را، با محتوای همه‌ی فایل‌های includeشده به ترتیب، چاپ می‌کند. وقتی نمی‌دانی یک تنظیم از کجا آمده، این ابزار توست:

Terminal window
nginx -T 2>/dev/null | grep '^# configuration file'
خروجی
# configuration file /etc/nginx/nginx.conf:
# configuration file /etc/nginx/mime.types:
# configuration file /etc/nginx/conf.d/status.conf:
# configuration file /etc/nginx/sites-enabled/default:
# configuration file /etc/nginx/sites-enabled/shop.test:
# configuration file /etc/nginx/snippets/lx-headers.conf:

ترتیب: اول nginx.conf، بعد mime.types، بعد conf.d (به ترتیب الفبا)، بعد sites-enabled (به ترتیب الفبا)، و فایل‌های snippets هر جا include شده‌اند. (ماژول‌های پویا هم اگر بودند، همین‌جا می‌آمدند.)

ارث‌بری: directive ای که در context بیرونی نوشته شود، به context های درونی می‌رسد، مگر اینکه درونی خودش همان directive را داشته باشد. این را با add_header می‌بینیم که یک استثنای معروف دارد:

/etc/nginx/conf.d/lx-inherit.conf
add_header X-From-Http "http-level" always;
server {
listen 8086;
location / {
return 200 "no add_header here\n";
}
location /own/ {
add_header X-From-Location "location-level" always;
return 200 "own add_header\n";
}
}
Terminal window
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
echo "=== /"
curl -s -i http://localhost:8086/ | grep -i '^x-from'
echo "=== /own/"
curl -s -i http://localhost:8086/own/ | grep -i '^x-from'
rm /etc/nginx/conf.d/lx-inherit.conf
systemctl reload nginx
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
=== /
X-From-Http: http-level
=== /own/
X-From-Location: location-level

add_header سطح http (چون فایل‌های conf.d داخل http include می‌شوند) به مسیر / رسید. ولی /own/ که add_header خودش را دارد، هدر سطح http را کاملاً از دست داد. قاعده‌ی add_header (و چند directive آرایه‌ای دیگر مثل proxy_set_header): اگر سطح درونی حتی یک add_header داشته باشد، هیچ‌کدام از سطح بیرونی به ارث نمی‌رسد. این دلیل خیلی از «چرا هدر امنیتی‌ام روی بعضی صفحه‌ها نیست؟»هاست؛ راه‌حل رایج همان include snippets/... در هر سطحی است که add_header دارد.

فایل‌ها و پوشه‌ها (Ubuntu):

مسیر محتوا کی خوانده می‌شود
/etc/nginx/nginx.conf فایل اصلی همیشه
/etc/nginx/conf.d/*.conf تنظیم‌های اضافه خودکار، داخل http
/etc/nginx/sites-available/ همه‌ی سایت‌ها هیچ‌وقت مستقیم
/etc/nginx/sites-enabled/ لینک سایت‌های فعال داخل http، همه‌ی فایل‌ها
/etc/nginx/snippets/ تکه‌های قابل استفاده‌ی دوباره فقط با include
/etc/nginx/mime.types پسوند ← Content-Type با include در http

دستورهای این درس:

دستور کار
sudo nginx -t آزمایش تنظیمات (نحو، فایل‌ها، پورت‌ها)
sudo nginx -T آزمایش + چاپ کل تنظیمات نهایی
sudo systemctl reload nginx اعمال بدون قطع
sudo ln -s /etc/nginx/sites-available/SITE /etc/nginx/sites-enabled/ روشن کردن سایت
sudo rm /etc/nginx/sites-enabled/SITE خاموش کردن سایت (فایل اصلی می‌ماند)
sudo nginx -t && sudo systemctl reload nginx الگوی امن بعد از هر تغییر

سطح پیام‌های Nginx (از کم‌اهمیت به مهم): debug، info، notice، warn، error، crit، alert، emerg.

۱) نوشتن فایل در sites-available و فراموش کردن لینک

Section titled “۱) نوشتن فایل در sites-available و فراموش کردن لینک”

فایل را نوشتی، reload کردی و هیچ تغییری نمی‌بینی. راه‌حل: ls -l /etc/nginx/sites-enabled/؛ اگر لینک نیست، sudo ln -s ....

Terminal window
cd /etc/nginx/sites-available
ln -s blog.test ../sites-enabled/blog.test
ls -l ../sites-enabled/blog.test
nginx -t 2>&1 | grep -E 'emerg' | sed -E 's/^[0-9/]+ [0-9:]+ //'
rm ../sites-enabled/blog.test
cd ~
خروجی
lrwxrwxrwx 1 root root 9 Oct 4 12:41 ../sites-enabled/blog.test -> blog.test
[emerg] 14776#14776: open() "/etc/nginx/sites-enabled/blog.test" failed (40: Too many levels of symbolic links) in /etc/nginx/nginx.conf:60

لینک نسبی نسبت به پوشه‌ی خود لینک حل می‌شود، نه پوشه‌ای که در آن بودی؛ پس sites-enabled/blog.test به blog.test در همان sites-enabled، یعنی به خودش، اشاره کرد و Nginx با Too many levels of symbolic links (حلقه‌ی لینک) شکست خورد. راه‌حل: همیشه مسیر مطلق: sudo ln -s /etc/nginx/sites-available/blog.test /etc/nginx/sites-enabled/.

۳) کپی کردن به‌جای لینک

Section titled “۳) کپی کردن به‌جای لینک”

اگر به‌جای لینک فایل را کپی کنی، دو نسخه داری؛ بعداً sites-available را ویرایش می‌کنی و هیچ اثری نمی‌بینی چون Nginx نسخه‌ی کپی را می‌خواند. راه‌حل: ls -l باید -> نشان دهد.

۴) فایل پشتیبان در sites-enabled

Section titled “۴) فایل پشتیبان در sites-enabled”
Terminal window
cp /etc/nginx/sites-available/shop.test /etc/nginx/sites-enabled/shop.test.bak
nginx -t 2>&1 | grep -E 'warn|emerg' | sed -E 's/^[0-9/]+ [0-9:]+ //'
rm /etc/nginx/sites-enabled/shop.test.bak
خروجی
[warn] 14781#14781: conflicting server name "shop.test" on 0.0.0.0:80, ignored

include sites-enabled/* همه‌ی فایل‌ها را می‌خواند، حتی .bak و ~؛ حالا دو server با یک اسم داری و هشدار conflicting server name می‌گیری (و یکی نادیده گرفته می‌شود). راه‌حل: پشتیبان را جای دیگری بگذار (مثلاً /root/)، نه در sites-enabled یا conf.d.

درس قبل: با تنظیم خراب، reload شکست می‌خورد (و تو فکر می‌کنی تغییرت اعمال شده). راه‌حل: sudo nginx -t && sudo systemctl reload nginx.

۶) گم شدن add_header ها با ارث‌بری

Section titled “۶) گم شدن add_header ها با ارث‌بری”

پشت پرده: یک add_header در location همه‌ی add_header های بیرونی را برای آن مسیر حذف می‌کند. راه‌حل: هدرهای مشترک را در یک snippet بگذار و در هر سطحی که add_header دارد include کن.

✎ تمرینآسان

تمرین اصلی درس: در فایل سایت shop.test یک خطای عمدی بساز (مثلاً ; آخر root را پاک کن)، با nginx -t آن را پیدا کن (پیام و شماره‌ی خط را بخوان)، درستش کن و ثابت کن nginx -t دوباره موفق است. در این فاصله سایت باید بالا بماند (چون reload نکرده‌ای).

دیدن جواب
Terminal window
f=/etc/nginx/sites-available/shop.test
sed -i 's|root /var/www/shop.test;|root /var/www/shop.test|' "$f"
grep -n 'root' "$f"
nginx -t
echo "--- سایت در این فاصله: $(curl -s --resolve shop.test:80:127.0.0.1 http://shop.test/)"
sed -i 's|root /var/www/shop.test$|root /var/www/shop.test;|' "$f"
nginx -t
خروجی
5: root /var/www/shop.test
2026/10/04 12:41:47 [emerg] 14787#14787: invalid number of arguments in "root" directive in /etc/nginx/sites-enabled/shop.test:6
nginx: configuration file /etc/nginx/nginx.conf test failed
--- سایت در این فاصله: <h1>shop.test</h1>
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

پیام به خط بعد از root اشاره کرد (index را جزو مقدارهای root دید و به‌خاطر تعداد مقدار غلط خطا داد). تا وقتی reload نکنی، Nginx با تنظیمات قبلی کار می‌کند؛ برای همین سایت در تمام این مدت بالا بود.

✎ تمرینمتوسط

سایت دوم blog.test را با فایل جدا در sites-available بساز (پوشه‌ی /var/www/blog.test با یک index.html)، روشنش کن و با curl --resolve نشان بده هر دو سایت (shop.test و blog.test) روی یک پورت جواب درست می‌دهند. بعد blog.test را خاموش کن، بدون اینکه فایلش پاک شود.

دیدن جواب
Terminal window
mkdir -p /var/www/blog.test
echo '<h1>blog.test</h1>' > /var/www/blog.test/index.html
cat > /etc/nginx/sites-available/blog.test <<'EOF'
server {
listen 80;
server_name blog.test;
root /var/www/blog.test;
index index.html;
}
EOF
ln -s /etc/nginx/sites-available/blog.test /etc/nginx/sites-enabled/
nginx -t 2>&1 | tail -n 1 && systemctl reload nginx
sleep 1
for h in shop.test blog.test; do
printf '%-10s -> ' "$h"
curl -s --resolve "$h:80:127.0.0.1" "http://$h/"
done
rm /etc/nginx/sites-enabled/blog.test
systemctl reload nginx
sleep 1
echo "--- بعد از خاموش کردن blog.test:"
curl -s --resolve blog.test:80:127.0.0.1 http://blog.test/ | grep -o '<title>.*</title>'
ls /etc/nginx/sites-available/ /etc/nginx/sites-enabled/
خروجی
nginx: configuration file /etc/nginx/nginx.conf test is successful
shop.test -> <h1>shop.test</h1>
blog.test -> <h1>blog.test</h1>
--- بعد از خاموش کردن blog.test:
<title>Welcome to nginx!</title>
/etc/nginx/sites-available/:
blog.test
default
shop.test
/etc/nginx/sites-enabled/:
default
shop.test

دو سایت روی یک پورت ۸۰، و Nginx با server_name (از هدر Host) بینشان فرق گذاشت. بعد از برداشتن لینک، blog.test به سایت پیش‌فرض رسید، ولی فایلش در sites-available ماند و با یک ln -s برمی‌گردد.

✎ تمرینسخت

با nginx -T یک اسکریپت کوچک بنویس که برای هر فایل تنظیماتی که Nginx واقعاً می‌خواند، تعداد بلوک‌های server و location و فهرست پورت‌های listen را گزارش کند. (راهنمایی: خروجی nginx -T هر فایل را با خط # configuration file PATH: شروع می‌کند؛ با awk آن را تکه کن.)

دیدن جواب
Terminal window
nginx -T 2>/dev/null | awk '
/^# configuration file / { file = $4; sub(/:$/, "", file); order[++n] = file; next }
{
line = $0
sub(/#.*/, "", line)
if (line ~ /^[ \t]*server[ \t]*\{/) servers[file]++
if (line ~ /^[ \t]*location[ \t]/) locations[file]++
if (line ~ /^[ \t]*listen[ \t]/) { sub(/^[ \t]*listen[ \t]+/, "", line); sub(/;.*/, "", line); ports[file] = ports[file] " " line }
}
END {
for (i = 1; i <= n; i++) {
f = order[i]
if (servers[f] + locations[f] == 0) continue
printf "%-42s server=%d location=%d listen:%s\n", f, servers[f], locations[f], ports[f]
}
}'
خروجی
/etc/nginx/conf.d/status.conf server=1 location=1 listen: 8085
/etc/nginx/sites-enabled/default server=1 location=1 listen: 80 default_server
/etc/nginx/sites-enabled/shop.test server=1 location=0 listen: 80

awk هر بار که به خط # configuration file می‌رسد، اسم فایل فعلی را عوض می‌کند و بقیه‌ی خط‌ها را به حساب همان فایل می‌گذارد. کامنت‌ها را اول با sub(/#.*/, "", line) برمی‌داریم تا خط‌های کامنت‌شده (مثل listen [::]:80 در default) شمرده نشوند. خروجی تصویر کوچکی از کل سرور می‌دهد: کدام فایل چند سایت و مسیر دارد و روی کدام پورت‌ها گوش می‌دهد.

⚡ بررسی سریع

فایل تنظیمات سایتی در sites-enabled با کدام بلوک شروع می‌شود و چرا؟

؟ آزمونک
  1. فرق sites-available و sites-enabled چیست؟

  2. nginx -t پیام «unexpected "index" in /etc/nginx/sites-enabled/shop:6» می‌دهد ولی خط ۶ درست است. اول کجا را نگاه می‌کنی؟

  3. پیام «"worker_connections" directive is not allowed here» یعنی چه؟

  4. در http هدر X-A را با add_header گذاشته‌ای و در یک location فقط X-B را. درخواست به آن location کدام هدرها را دارد؟

  5. فایل shop.conf.bak را در sites-enabled کپی کرده‌ای. چه می‌شود؟

  6. می‌خواهی ببینی یک تنظیم از کدام فایل آمده و تنظیمات نهایی بعد از همه‌ی include ها چیست. کدام دستور؟

  • زبان تنظیمات دو جزء دارد: directive (name value;، با ; اجباری) و context (name { ... }). context های اصلی: main ← events و http ← server ← location. هر directive فقط در context های مشخصی مجاز است.
  • nginx.conf در Ubuntu داخل http این‌ها را include می‌کند: conf.d/*.conf (خودکار) و sites-enabled/* (لینک‌ها). پس فایل سایت با server شروع می‌شود.
  • روشن کردن سایت: فایل در sites-available و sudo ln -s /etc/nginx/sites-available/SITE /etc/nginx/sites-enabled/ (مسیر مطلق). خاموش: rm لینک.
  • conf.d برای تنظیم‌های سراسری http و ایمیج داکر؛ snippets + include برای تکه‌های تکراری.
  • ارث‌بری: تنظیم بیرونی به درونی می‌رسد مگر درونی خودش داشته باشد؛ add_header در سطح درونی همه‌ی بیرونی‌ها را حذف می‌کند.
  • sudo nginx -t خطا را با فایل و شماره‌ی خط می‌گوید (خط قبل را هم ببین)؛ nginx -T تنظیمات نهایی را چاپ می‌کند؛ بعد sudo systemctl reload nginx.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
sudo nginx -tآزمایش تنظیمات، با فایل و شماره‌ی خط خطا
sudo nginx -T | grep '^# configuration file'فایل‌هایی که واقعاً خوانده می‌شوند
sudo systemctl reload nginxاعمال تنظیمات بدون قطع
sudo ln -s /etc/nginx/sites-available/shop.test /etc/nginx/sites-enabled/روشن کردن یک سایت
sudo rm /etc/nginx/sites-enabled/shop.testخاموش کردن سایت
ls -l /etc/nginx/sites-enabled/سایت‌های فعال (باید -> نشان دهد)
include snippets/headers.conf;آوردن یک تکه‌ی مشترک
return 200 "ok\n";پاسخ مستقیم بدون فایل
curl --resolve shop.test:80:127.0.0.1 http://shop.test/آزمایش یک دامنه بدون DNS
grep -nvE '^\s*(#|$)' /etc/nginx/nginx.confخواندن فایل بدون کامنت