کدنامهمرجع‌های مهندسی نرم‌افزار، به فارسی
Nginx · فصل ۲Nginx · Chapter 2

دستور زبان کانفیگ و context‌ها

Config syntax and contexts

یک خط را به کانفیگ اضافه می‌کنی، Nginx می‌گوید «این‌جا مجاز نیست». منظورش از «اینجا» کجاست؟ این فصل فایل کانفیگ را مثل یک برنامهٔ کوچک می‌خواند: دستورها کجا معتبرند، بلوک‌ها چطور تو‌در‌تو می‌شوند و کدام مقدار چه زمانی معنا پیدا می‌کند.

You add one line to the configuration and Nginx says, “not allowed here.” What does “here” mean? This chapter treats the config as a small program: where directives are valid, how blocks nest, and when a value actually takes meaning.

۷۰دقیقهٔ مطالعهminutes
۱۸تمرین با پاسخsolved exercises
۲نمودار مفهومیconcept diagrams
۱آزمایش خراب‌کردن امنsafe failure experiment

یک خط درست، در جای اشتباهA valid-looking line in the wrong place

در فصل قبل با nginx -t دیدی که می‌شود پیش از reload پیکربندی را بررسی کرد. حالا یک همکار می‌گوید: «برای اینکه workerها بیشتر connection بگیرند، worker_connections 1024; را به config اضافه کردم. اما تست خطا می‌دهد.» خط خودش عجیب نیست؛ شاید فقط در جای نامناسبی نوشته شده باشد.

In the previous chapter, you used nginx -t to test a configuration before reloading. Now a teammate says, “I added worker_connections 1024; so workers can handle more connections, but the test fails.” The line itself looks reasonable; perhaps it is simply in the wrong place.

پیش از اینکه جابه‌جایش کنیم، پیش‌بینی کن: آیا همهٔ دستورهای Nginx را می‌شود هر جای فایل نوشت؟ اگر جواب «نه» است، Nginx از کجا می‌فهمد این خط در context نامعتبر قرار گرفته؟

Before moving it, predict: can every Nginx directive go anywhere in the file? If not, how does Nginx know that this line is in an invalid context?

مدل سادهThe simple model

کانفیگ فقط فهرست تنظیمات نیست؛ ساختار تو‌در‌تویی دارد. هر دستور در یک یا چند جای مشخص مجاز است. آکولادها آن «جا»ها را می‌سازند و parser هنگام خواندن فایل، جای هر دستور را می‌داند.

A configuration is not just a list of settings; it has a nested structure. Each directive is valid in one or more defined places. Braces create those places, and the parser knows where each directive appears as it reads the file.

directive و block چه فرقی دارند؟What is the difference between a directive and a block?

در Nginx، واحدهای اصلی config «directive» هستند. بعضی یک دستور ساده‌اند و با ; تمام می‌شوند؛ بعضی block باز می‌کنند و directiveهای دیگری را داخل آکولاد جا می‌دهند. یک فاصلهٔ اضافه معمولاً مشکل نیست؛ جاافتادن نقطه‌ویرگول یا آکولاد می‌تواند ساختار را عوض کند.

The basic units of Nginx configuration are directives. Some are simple statements ending in ;; others open blocks that contain more directives inside braces. Extra whitespace is usually harmless; a missing semicolon or brace can change the structure.

شکلFormنمونهExampleچه چیزی را می‌گوید؟What it means
directive سادهSimple directiveworker_processes 1;یک نام، مقدارها و پایان با ;A name, arguments, and a terminating semicolon
blockevents { ... }یک directive که بدنه‌اش با { } مشخص می‌شود؛ معمولاً ; بعد از آکولاد بسته نمی‌آید.A directive whose body is delimited by braces; the closing brace is not normally followed by a semicolon.
comment# فقط برای خوانندهبرای توضیح به انسان است و بخشی از تنظیمات اجرایی نیست.It is for human readers and is not an active setting.

هرجا # آغاز یک comment باشد، ادامهٔ خط توضیح است و Nginx آن را مثل directive اجرا نمی‌کند. comment برای مستندسازی config است؛ اگر خطی را با آن غیرفعال می‌کنی، مطمئن شو علامت پیش از directive آمده و آن خط دیگر به‌عنوان تنظیم فعال parse نمی‌شود.

When # begins a comment, the rest of the line is explanatory text, not an active directive. Comments document the config; if you use one to disable a line, make sure it precedes the directive so that line is no longer parsed as active configuration.

این قطعه را بخوان. هنوز لازم نیست همهٔ directiveها را حفظ باشی؛ فقط دنبال شکل و مرز بلوک‌ها بگرد:

Read this fragment. You do not need to memorize every directive yet; just trace the syntax and block boundaries:

a small configuration skeleton
# Main context: a simple directive
worker_processes 1;

events {
    worker_connections 128;
}

http {
    default_type text/plain;

    server {
        listen 127.0.0.1:8088;

        location / {
            return 200 "Nginx is ready";
        }
    }
}

نقطه‌ویرگول بعد از worker_processes، worker_connections، default_type، listen و return پایان هر دستور ساده است. اما بعد از آکولادهای بسته، نقطه‌ویرگول نمی‌بینی. اگر یکی را جا بیندازی، ممکن است تست خطا بدهد؛ پیام خطا معمولاً نام directive یا محل ساختاری مشکل را سرنخ می‌دهد، ولی همیشه دقیقاً همان کاراکتری را که جا انداخته‌ای نشان نمی‌دهد.

The semicolons after worker_processes, worker_connections, default_type, listen, and return terminate simple directives. Closing braces do not have semicolons after them. If one is missing, the test may fail; the message usually points to a directive or structural location, but it does not always identify the exact character you omitted.

یک config فقط وقتی معنی دارد که از بیرون به داخل بخوانی. http در سطح اصلی است؛ server داخل http؛ و location داخل server. directiveای که داخل یک block است، در همان context و فرزندان مجازش قرار دارد، نه در همهٔ فایل.

A config makes sense when read from the outside in. http is at the main level; server is inside http; and location is inside server. A directive inside a block belongs to that context and its permitted descendants, not to every part of the file.

contextها: دستور را کجا می‌توان گذاشت؟Contexts: where may a directive live?

از نام بلوک‌ها یک درخت می‌سازیم، نه اینکه آن‌ها را یک فهرست تخت فرض کنیم. همهٔ شاخه‌ها در هر نصب دقیقاً یکسان نیستند؛ این درخت، چهار context اصلی مورد نیاز این فصل و جایگاه معمول directiveهای مرتبط با HTTP را نشان می‌دهد.

Build a tree from the block names instead of treating them as a flat list. Not every Nginx build has precisely the same branches; this tree shows the four core contexts for this chapter and the usual nesting for HTTP directives.

Nginx configuration contexts and nesting / درخت contextهای پیکربندی Nginx main context events http server location worker_connections HTTP-level settings
فلش یعنی بلوک پایینی را می‌توان در بدنهٔ context بالایی نوشت. events و http هر دو در main هستند؛ location زیرمجموعهٔ server است.An arrow means the lower block may appear inside the upper context. events and http are both under main; location is nested under server.

درخت می‌گوید «کجا». اما آیا هر directive اجازه دارد در هر گره قرار بگیرد؟ نه. هر directive، فهرست contextهای مجاز خودش را دارد؛ برای نمونه worker_connections متعلق به events است، listen معمولاً در server می‌آید و return می‌تواند در server یا location باشد. مستندات همان directive مرجع نهایی است.

The tree tells us “where,” but may every directive appear in every node? No. Each directive has its own list of valid contexts: for example, worker_connections belongs in events, listen is normally in server, and return can be used in server or location. The directive’s documentation is the authority.

contextنقشش در این مدلRole in this modelنمونهٔ directiveExample directive
mainسطح بیرونی فایلThe outermost configuration levelworker_processes
eventsتنظیمات ارتباط و workerهاConnection and worker settingsworker_connections
httpتنظیمات ماژول HTTP و serverهاHTTP module settings and serversdefault_type
serverتنظیمات یک HTTP server blockOne HTTP server block’s settingslisten
locationتنظیمات وابسته به مسیر درخواستSettings associated with a request URIreturn

به این جدول مثل نقشهٔ شروع نگاه کن، نه مرجع کامل همهٔ directiveها. مثلاً root در http، server و location مجاز است؛ جای دقیقش روی مقداری که در سطح پایین‌تر تعریف شده اثر می‌گذارد. فصل چهارم رفتار انتخاب location را جداگانه باز می‌کند.

Treat this table as a starting map, not a complete reference for every directive. For example, root is valid in http, server, and location; its placement matters when a lower level defines its own value. Chapter 4 will examine location selection separately.

بگذار خود Nginx بگوید خط کجاستLet Nginx tell us what is wrong

حالا همان اشتباه همکارت را کوچک و جدا از سرویس زنده بازسازی می‌کنیم. worker_connections را عمداً داخل http می‌گذاریم. پیش‌بینی: چون directive درست نوشته شده، تست می‌گذرد؟

Let us reproduce your teammate’s mistake in a small, isolated file, away from the live service. We will deliberately put worker_connections inside http. Predict: because the directive is spelled correctly, will the test pass?

bad.conf — deliberately wrong context
CTX_DIR="$(mktemp -d /tmp/nginx-context.XXXXXX)"
mkdir -p "$CTX_DIR/logs"
cat > "$CTX_DIR/bad.conf" <<'NGINX'
worker_processes 1;
error_log logs/error.log notice;
events {}
http {
    worker_connections 64;
}
NGINX

nginx -t -p "$CTX_DIR/" -c "$CTX_DIR/bad.conf"
nginx: [emerg] "worker_connections" directive is not allowed here in /tmp/nginx-context.xxxxxx/bad.conf:5
nginx: configuration file /tmp/nginx-context.xxxxxx/bad.conf test failed

عبارت تعیین‌کننده is not allowed here است: parser directive را می‌شناسد، اما context فعلی را برایش مجاز نمی‌داند. این با «unknown directive» فرق دارد که می‌تواند از غلط املایی یا نبود ماژول لازم خبر بدهد. خروجی نمونه است؛ مسیر و شمارهٔ خط روی ماشین تو فرق می‌کند.

The key phrase is is not allowed here: the parser recognizes the directive but rejects its current context. That differs from “unknown directive,” which may indicate a typo or missing module. The output is representative; paths and line numbers vary by host.

برای تعمیر، فقط جابه‌جایی لازم را انجام می‌دهیم: این تنظیم به events می‌رود. بعد همان آزمون را تکرار می‌کنیم؛ پاسخ درست، موفق‌شدن parse همین فایل است، نه اثبات کارکرد هر سرویس HTTP.

The repair is to move only this setting into events. Then repeat the same test. A passing result proves this file parses; it does not prove that an HTTP service is serving the intended response.

corrected context
cat > "$CTX_DIR/good.conf" <<'NGINX'
worker_processes 1;
error_log logs/error.log notice;
events {
    worker_connections 64;
}
http {}
NGINX

nginx -t -p "$CTX_DIR/" -c "$CTX_DIR/good.conf"
nginx: the configuration file /tmp/nginx-context.xxxxxx/good.conf syntax is ok
nginx: configuration file /tmp/nginx-context.xxxxxx/good.conf test is successful

اگر باز هم خطا می‌بینی، شمارهٔ خط را در همان فایل دنبال کن و بررسی کن که اشتباهی آکولاد را زودتر نبسته باشی. برای ادامهٔ تحقیق، nginx -T فایل‌های includeشده را هم نشان می‌دهد؛ اگر مسئله از یک directive باشد، صفحهٔ مستندات همان directive بخش Context را دارد.

If it still fails, inspect the reported line and check whether an earlier brace closed a block too soon. For further investigation, nginx -T also shows included files; the directive’s documentation lists its allowed contexts under “Context.”

بعد از دیدن هر دو نتیجه، فقط پوشه‌ای را که همین mktemp ساخته پاک کن؛ config زنده را تغییر ندادیم و این آزمایش هم سروری بالا نیاورد.

After seeing both results, remove only the directory created by this mktemp. We did not change the live config or start a server for this experiment.

remove the isolated test files
printf 'Review target before cleanup: %s\n' "$CTX_DIR"
rm -r -- "$CTX_DIR"

include فایل را به جایش می‌خواندinclude reads a file at its insertion point

وقتی فایل config بزرگ شد، طبیعی است تنظیم مشترک را در فایلی جدا بگذاری. یک نکتهٔ مهم: include یک context تازه نمی‌سازد. محتوای فایلِ واردشده انگار در همان نقطه نوشته شده؛ پس context مجازِ directiveهای درون آن، از جای include می‌آید.

As a config grows, it is natural to put shared settings in separate files. The important detail is that include does not create a new context. The included file behaves as though its contents were written at that exact point, so the valid context for its directives comes from where the include appears.

این دو تکه را مقایسه کن. اولی را می‌شود داخل http گذاشت؛ دومی را باید در context مناسبِ خودش نگه داشت. پس پسوند .conf یا اسم فایل، مجازبودن محتوا را تعیین نمی‌کند.

Compare these two fragments. The first can be included inside http; the second belongs in its own valid context. A .conf suffix or filename does not make its contents valid everywhere.

include در بدنهٔ httpInclude inside http

محتوای نمونه: default_type text/plain;. پس از include، این directive همچنان در contextِ http قرار دارد.

Example content: default_type text/plain;. After inclusion, this directive is still in the http context.

همان محتوا در eventsThe same content inside events

اگر همان فایل را آنجا include کنی، default_type به‌خاطر context نادرست رد می‌شود.

If you include that same file there, default_type is rejected because the context is wrong.

برای دیدن فایل‌های مؤثر، در config آزمایشی یا نصب فعال از nginx -T استفاده کن. سرآیندهایی که نام فایل را چاپ می‌کنند نشان می‌دهند کدام فایل‌ها باز شده‌اند. یک فایلِ موجود روی دیسک تا وقتی با include به درخت وصل نشده، تنظیم فعال نیست؛ و یک include موفق هم به‌تنهایی نمی‌گوید درخواست بعدی چطور پاسخ می‌گیرد.

Use nginx -T on the test configuration or active installation to inspect the effective files. Filename headers show which files were opened. A file on disk is not an active setting until it is connected with include; and a successful include alone does not tell us how the next request will be handled.

خود directiveِ include در هر context قابل استفاده است؛ این اجازه به معنی آزادبودن محتوای فایل نیست. فایل includeشده باید در نقطه‌ای که وارد شده معتبر باشد و directiveهایش در همان محدوده بررسی می‌شوند.

The include directive itself is valid in any context; that does not make the included file’s contents unrestricted. The file must be valid at the insertion point, and its directives are checked in that same scope.

ارث‌بری دستورها قانون واحدی نداردDirectives do not all inherit the same way

یک config والد برای چند سایت تعریف کرده‌ای. انتظار طبیعی این است که تنظیم‌هایش به همهٔ فرزندان برسند. بعضی directiveها چنین می‌کنند؛ بعضی دیگر، اگر در context پایین‌تر دوباره تعریف شوند، تنظیم‌های والد را به شکل مورد انتظار تو ترکیب نمی‌کنند. باید رفتار همان directive را بخوانی، نه اینکه از روی آکولادها حدس بزنی.

You define a shared parent configuration for several sites. It is natural to expect every setting to flow into every child. Some directives do; others, when defined again in a lower context, do not combine with the parent values the way you might expect. Check the rule for that directive instead of guessing from the braces.

برای مثال، root از سطح بالاتر می‌آید وقتی در سطح فعلی دوباره نوشته نشده باشد. اگر در server مقدار بدهی و در location چیزی ننویسی، همان مقدار به کار می‌رود. اما add_header طبق رفتار استانداردش، فقط وقتی از سطح قبل به ارث می‌رسد که در سطح جاری هیچ add_headerای تعریف نشده باشد.

For example, root is inherited from a higher level when it is not redefined at the current level. Set it in server and omit it in location, and that value is used. By contrast, under the standard add_header inheritance rule, parent values are inherited only when the current level defines no add_header directives.

read inheritance one level at a time
http {
    add_header X-From-HTTP "parent" always;

    server {
        add_header X-From-Server "server" always;

        location / {
            # No local add_header: inherits from server level.
            return 200 "root is inherited when not redefined";
        }

        location /special/ {
            add_header X-From-Location "location" always;
            return 200 "this level defines its own add_header";
        }
    }
}

در پاسخ location اول، X-From-Server را انتظار داریم؛ X-From-HTTP در سطح server با تعریف محلیِ add_header کنار گذاشته شده. در location دوم، تعریف محلی خودش باعث می‌شود X-From-Server هم به آن سطح نرسد؛ فقط header همان location می‌ماند. پارامتر always را گذاشتیم تا افزودن header به statusهای خاص محدود نشود.

For the first location, expect X-From-Server; X-From-HTTP was not inherited into the server level because that level defined its own add_header. In the second location, its local directive means X-From-Server is not inherited there either; only the location’s header remains. We used always so header addition is not limited to a subset of response statuses.

یک تلهٔ نسخه‌ایA version-sensitive detail

در Nginx جدید، directive جداگانهٔ add_header_inherit می‌تواند قاعدهٔ ارث‌بری add_header را تغییر دهد؛ گزینهٔ merge از نسخهٔ 1.29.3 اضافه شده است. این قابلیت را روی سرور قدیمی فرض نکن. نمونهٔ بالا رفتار پیش‌فرض مستندشده را می‌گوید؛ اگر config تو این directive را دارد، همان قاعدهٔ تغییر‌یافته را هم لحاظ کن.

Recent Nginx versions provide add_header_inherit to change the inheritance rule; its merge option was added in 1.29.3. Do not assume this exists on an older server. The example shows the documented default; if your config uses this directive, account for its changed rule.

پس برای هر directive دو سؤال بنویس: در کدام contextها مجاز است؟ اگر در سطح پایین‌تر دوباره بیاید، مقدار قبلی حفظ، جایگزین یا ترکیب می‌شود؟ پاسخ را از مستندات رسمی همان directive و نسخهٔ نصب‌شده بگیر. برای مثال‌های دقیق root و add_header، پیوندهای پایان فصل را ببین.

For every directive, ask two questions: which contexts allow it, and if it appears lower down, is the parent value kept, replaced, or merged? Find the answer in that directive’s official documentation and your installed version. See the references at the end for the exact root and add_header rules.

متغیرها: هنگام درخواست، نه مثل قالب‌سازVariables: evaluated for requests, not used as a template engine

در خط return 200 "path=$uri"; مقدار $uri از یک درخواست می‌آید. Nginx این مقدار را مثل یک جای‌نگهدار متنی هنگام خواندن config با یک رشتهٔ ثابت جایگزین نمی‌کند؛ متغیرهای Nginx معمولاً در جریان پردازش درخواست معنا پیدا می‌کنند.

In return 200 "path=$uri";, $uri comes from a request. Nginx does not replace it with one fixed string while reading the config like a text-template engine; Nginx variables usually become meaningful during request processing.

اگر پیکربندی شامل این خط باشد، nginx -t می‌تواند syntax را بپذیرد؛ اما تا وقتی درخواست نیاید، مقدار واقعی $uri را نمی‌داند. دو درخواست به pathهای متفاوت می‌توانند دو مقدار متفاوت بسازند. این نتیجه به معنی آن نیست که هر متغیری را در هر directive می‌شود گذاشت؛ پشتیبانی متغیر به همان directive وابسته است.

If the config contains this line, nginx -t can accept its syntax, but the actual value of $uri is not known until a request arrives. Requests for different paths can produce different values. That does not mean every variable is supported by every directive; support depends on the directive.

a request-time value
location / {
    default_type text/plain;
            return 200 "path=$uri";
}

curl -i http://127.0.0.1:8088/docs/start
HTTP/1.1 200 OK
...
path=/docs/start

بدنهٔ path=/docs/start مدرکِ ارزیابی متغیر برای همین درخواست است؛ ثابت نمی‌کند configهای دیگر یا درخواست با path دیگری همان خروجی را دارند. همچنین متغیرها راه مناسب تکرارنکردن متن ثابت نیستند. برای بخش‌های ثابت و تکراری، فایل‌های includeشده یا ابزار بیرونیِ تولید config انتخاب روشن‌تری‌اند.

The path=/docs/start body proves the variable was evaluated for this request; it does not prove that other requests or configurations produce the same result. Variables are also not a good way to avoid repeating static text. Included files or an external configuration generator are clearer tools for reusable static fragments.

دو زمان را قاطی نکنKeep the two moments separate

اول Nginx فایل‌ها را می‌خواند و ساختار و مجازبودن directiveها را بررسی می‌کند. بعد، هنگام رسیدن درخواست، بعضی مقدارها مثل $uri برای همان درخواست محاسبه می‌شوند. پذیرفته‌شدن مرحلهٔ اول، پاسخ مرحلهٔ دوم را تضمین نمی‌کند.

First Nginx reads files and checks their structure and directive validity. Later, when a request arrives, some values such as $uri are evaluated for that request. Passing the first phase does not guarantee the response in the second.

Configuration parsing precedes request processing / خواندن config پیش از پردازش درخواست Two different moments read config filesstart / reload / nginx -t validate structuredirective + context time passes request arrivesevaluate request values parse + validate before requests evaluate $uri per request $uri is not one fixed config-time string
آزمون پیکربندی در سمت چپ است؛ مقدارهایی مثل $uri سمت راست و برای هر درخواست محاسبه می‌شوند.Configuration testing belongs on the left; values such as $uri are evaluated on the right, for each request.

یک پیکربندی کوچک را خودت بسازBuild a small configuration yourself

به‌جای دست‌کاری سرویس اصلی، یک Nginx آزمایشی روی پورت 18088 بالا می‌آوریم. پیشوند موقت کمک می‌کند pid و logهای همین آزمایش از سرویس معمولی جدا باشند. اگر پورت اشغال است، عدد دیگری بالاتر از 1024 انتخاب کن و در هر دو جا یکسان به کار ببر.

Instead of changing the main service, we will run a test Nginx on port 18088. A temporary prefix keeps this experiment’s PID and logs separate from the normal service. If the port is already in use, choose another port above 1024 and use it consistently.

دایرکتوری موقتی و سه زیرپوشه می‌سازیم. متغیر پوسته فقط مسیر همین آزمایش است؛ پیش از پاک‌سازی آن را چاپ می‌کنیم تا معلوم باشد چه چیزی را ساخته‌ایم.

Create a temporary directory with three subdirectories. The shell variable holds only this experiment’s path; we will print it before cleanup so the created target is explicit.

prepare an isolated workspace
LAB_DIR="$(mktemp -d /tmp/codenames-nginx.XXXXXX)"
mkdir -p "$LAB_DIR/logs" "$LAB_DIR/snippets" "$LAB_DIR/html"
printf 'Lab directory: %s\n' "$LAB_DIR"

پیش از اینکه همه‌چیز را با هم بنویسیم، config را سه پله بزرگ می‌کنیم. در پلهٔ اول فقط main، events و یک http خالی داریم. این فایل باید parse شود، هرچند هنوز هیچ serverی ندارد:

Before writing everything at once, grow the configuration in three steps. At step one, we have only main, events, and an empty http block. It should parse even though no server exists yet:

stage 1 — outer contexts
cat > "$LAB_DIR/stage-1.conf" <<'NGINX'
worker_processes 1;
error_log logs/error.log notice;
events { worker_connections 64; }
http {}
NGINX
nginx -t -p "$LAB_DIR/" -c "$LAB_DIR/stage-1.conf"
... syntax is ok
... test is successful

یک server را داخل http می‌گذاریم و فقط یک پورت محلی معرفی می‌کنیم. اگر اشتباهی آن را بیرون از http بگذاری، تست باید پیش از هر درخواست جلویت را بگیرد:

Now place a server inside http and give it a local port. If you accidentally put it outside http, the test should stop you before any request is sent:

stage 2 — add server
cat > "$LAB_DIR/stage-2.conf" <<'NGINX'
worker_processes 1;
error_log logs/error.log notice;
events { worker_connections 64; }
http {
    server { listen 127.0.0.1:18088; }
}
NGINX
nginx -t -p "$LAB_DIR/" -c "$LAB_DIR/stage-2.conf"
... syntax is ok
... test is successful

حالا یک location داخل همان server می‌آید و پاسخ کوچکی می‌سازد. این بار هنوز سرور را start نکرده‌ایم؛ سه بار -t فقط می‌گوید ساختار هر سه پله معتبر بوده است. برای دیدن رفتار درخواست، در مرحلهٔ بعد نسخهٔ کامل را بالا می‌آوریم.

Finally, add a location inside that server and make it return a small response. We still have not started the server: three runs of -t only show that the three structures are valid. We will start the full version next to observe request behavior.

stage 3 — add location
cat > "$LAB_DIR/stage-3.conf" <<'NGINX'
worker_processes 1;
error_log logs/error.log notice;
events { worker_connections 64; }
http {
    server {
        listen 127.0.0.1:18088;
        location / { return 200 "ready"; }
    }
}
NGINX
nginx -t -p "$LAB_DIR/" -c "$LAB_DIR/stage-3.conf"
... syntax is ok
... test is successful

حالا یک include می‌سازیم که از context http وارد می‌شود. فایل فقط یک directive معتبر همان context دارد؛ پس include عملاً آن directive را در همان نقطه وارد می‌کند.

Now create an include inserted from the http context. The file contains a directive valid at that context, so inclusion effectively places it at that point.

snippets/shared-http.conf
cat > "$LAB_DIR/snippets/shared-http.conf" <<'NGINX'
add_header X-Lesson-Base "from-http" always;
NGINX

فایل اصلی را بساز. در این‌جا add_header در سطح http از طریق include می‌آید؛ بلوک server یک header دیگر و location هم یکی دیگر دارد تا اثر تعریف در سطح پایین‌تر را ببینیم.

Create the main file. Here, add_header enters the http level through an include; the server and location each define another header so we can observe the effect of lower-level definitions.

main experiment config
cat > "$LAB_DIR/nginx.conf" <<NGINX
worker_processes 1;
error_log $LAB_DIR/logs/error.log notice;
pid $LAB_DIR/logs/nginx.pid;

events {
    worker_connections 64;
}

http {
    include $LAB_DIR/snippets/*.conf;
    default_type text/plain;

    server {
        listen 127.0.0.1:18088;
        server_name localhost;
        add_header X-Lesson-Server "from-server" always;

        location / {
            return 200 "uri=\$uri";
        }

        location /local/ {
            add_header X-Lesson-Location "from-location" always;
            return 200 "uri=\$uri";
        }
    }
}
NGINX

این config را با همان پیشوند و فایل آزمایش می‌کنیم؛ -t درخواست واقعی نمی‌فرستد و فقط امکان parse و بازکردن فایل‌های includeشده را می‌سنجد. باید پیام موفق ببینی؛ اگر خطایی به مسیر snippet اشاره می‌کند، اول مطمئن شو مسیر چاپ‌شدهٔ LAB_DIR با مسیر داخل فایل یکی است.

Test this config with the same prefix and file. -t does not send a real request; it checks parsing and opening included files. Expect a success message. If an error names the snippet path, first make sure the printed LAB_DIR matches the path written into the file.

syntax test and effective config
nginx -t -p "$LAB_DIR/" -c "$LAB_DIR/nginx.conf"
nginx -T -p "$LAB_DIR/" -c "$LAB_DIR/nginx.conf"
nginx: the configuration file .../nginx.conf syntax is ok
nginx: configuration file .../nginx.conf test is successful
# configuration file .../snippets/shared-http.conf:
add_header X-Lesson-Base "from-http" always;

در خروجی -T باید نام snippet و خط add_header را ببینی. این ثابت می‌کند فایل include شده و نحو آن پذیرفته شده؛ هنوز فرایند آزمایشی بالا نیست. حالا آن را اجرا می‌کنیم و دو درخواست می‌فرستیم.

In -T, look for the snippet filename and its add_header line. This proves the file was included and accepted syntactically; the test process is not running yet. Start it and send two requests.

یادت باشد nginx -t پورت را رزرو نمی‌کند. پیش از اجرا بررسی کن برنامهٔ دیگری روی 18088 گوش ندهد؛ اگر خروجی یک listener نشان داد، پورت دیگری انتخاب کن و در config و curl هر دو را یکسان عوض کن. نبودن ردیف listener یعنی این بررسی فعلاً پورت اشغال‌شده‌ای ندیده است؛ تضمین نمی‌کند چند لحظهٔ بعد برنامهٔ دیگری سراغ آن نرود.

Remember that nginx -t does not reserve the port. Before starting, check that nothing else listens on 18088. If a listener appears, choose another port and change it consistently in the config and curl commands. No listener row means this check did not see one at that moment; it cannot guarantee that another program will not claim the port a moment later.

check port, start, and observe
ss -ltn '( sport = :18088 )'
nginx -p "$LAB_DIR/" -c "$LAB_DIR/nginx.conf"
curl -i http://127.0.0.1:18088/docs/start
curl -i http://127.0.0.1:18088/local/check
State  Recv-Q Send-Q Local Address:Port  Peer Address:Port

HTTP/1.1 200 OK
X-Lesson-Server: from-server
...
uri=/docs/start

HTTP/1.1 200 OK
X-Lesson-Location: from-location
...
uri=/local/check

در درخواست اول header سطح server را می‌بینی، نه header سطح http: چون server خودش add_header تعریف کرده، مقدار والد با رفتار پیش‌فرض به آن سطح منتقل نشده. در درخواست دوم فقط header سطح location می‌آید. دو path هم دو مقدار متفاوت برای $uri ساخته‌اند. این آزمایش نشان می‌دهد تفسیر config و پاسخ درخواست دو مرحله‌اند؛ پاسخ موفق هم فقط همین میزبان و همین درخواست‌ها را ثابت می‌کند.

The first request shows the server-level header, not the HTTP-level header: because the server defines its own add_header, the parent value is not carried into that level under the default rule. The second request shows only the location-level header. The two paths also produce different $uri values. This experiment separates config parsing from request handling; a successful response proves only these requests on this host.

برای بستن همان فرایند آزمایشی، به‌جای kill دستی از graceful quit همین config استفاده کن. چون پایان graceful ممکن است چند لحظه طول بکشد، پیش از پاک‌کردن پوشه صبر می‌کنیم تا فایل PID واقعاً ناپدید شود.

To stop this test process, use a graceful quit for this exact config instead of killing a random PID. A graceful exit may take a moment, so wait for the PID file to disappear before removing the directory.

targeted cleanup
nginx -p "$LAB_DIR/" -c "$LAB_DIR/nginx.conf" -s quit
for _ in $(seq 1 25); do
    [ ! -e "$LAB_DIR/logs/nginx.pid" ] && break
    sleep 0.2
done
if [ -e "$LAB_DIR/logs/nginx.pid" ]; then
    echo "PID file still exists; inspect the test server before cleanup"
    exit 1
fi
echo "test server stopped"
printf 'Review before removing: %s\n' "$LAB_DIR"
rm -r -- "$LAB_DIR"
test server stopped
Review before removing: /tmp/codenames-nginx.xxxxxx
فقط همان مسیر آزمایشOnly the lab directory

فرمان آخر را فقط پس از دیدن مسیری اجرا کن که خود mktemp ساخته و همین تمرین استفاده کرده است. مقدار خالی یا مسیر دیگری را جایگزین نکن؛ پوشهٔ واقعی سرویس را پاک نکن.

Run the final command only after reviewing the path created by mktemp and used by this lab. Do not substitute an empty or different path, and never remove the real service directory.

تمرین‌ها: config را بخوان، نه اینکه حدس بزنیExercises: read the config instead of guessing

در پاسخ‌هایت فقط نگو «این خط مجاز است». به context اشاره کن و بگو تست یا پاسخ HTTP دقیقاً چه چیزی را ثابت می‌کند. جواب‌ها را از پایین به بالا مرور کردیم تا نیمهٔ دوم هم به اندازهٔ شروع فصل دلیل و مثال داشته باشد.

Do not merely say, “this line is valid.” Name the context and explain what the test or HTTP response actually proves. The solutions are reviewed from the end upward so the second half keeps the same reasoning and examples as the opening.

۱. پیش‌بینی ساختار1. Predict the structure

در این config چند directive ساده و چند block می‌بینی؟ آیا بعد از } پایانی باید ; بیاید؟

How many simple directives and blocks do you see here? Should the final } be followed by a semicolon?

snippet
events { worker_connections 32; }
پاسخ را ببینShow solution

یک block به نام events و یک directive سادهٔ worker_connections 32; داریم. آکولاد بسته block را می‌بندد و خودش نقطه‌ویرگول نمی‌گیرد؛ directive داخلی دارد.

There is one events block and one simple worker_connections 32; directive. The closing brace ends the block and has no semicolon; the directive inside does.

۲. جای listen2. Where does listen belong?

یک نفر listen 8080; را کنار worker_processes در سطح اصلی نوشته. چه می‌کنی پیش از اینکه آن را جابه‌جا کنی؟

Someone put listen 8080; beside worker_processes at the main level. What do you check before moving it?

پاسخ را ببینShow solution

context مجاز directive را در مستندات یا پیام nginx -t می‌سنجم؛ listen متعلق به server block است، نه main. بعد ساختار را اصلاح و همان فایل را دوباره با همان باینری/گزینه‌ها تست می‌کنم.

Check the directive’s allowed context in its documentation or the nginx -t message. listen belongs in a server block, not main. Then fix the structure and retest the same file with the same binary and options.

۳. نقطه‌ویرگول جاافتاده3. A missing semicolon

فایل به این خط می‌رسد: default_type text/plain و در خط بعد server {. آیا می‌توانی بدون آزمون بگویی تنها خطا همین‌جاست؟

The file contains default_type text/plain followed by server {. Can you declare this the only error without testing?

پاسخ را ببینShow solution

این خط به احتمال زیاد نقطه‌ویرگول کم دارد، اما نتیجه را با nginx -t می‌گیریم و پیام و شمارهٔ خط را می‌خوانیم؛ خطاهای ساختاری قبلی ممکن است محل گزارش را جابه‌جا کنند. پس از اصلاح، آزمون را تکرار می‌کنیم.

The line likely lacks a semicolon, but confirm with nginx -t and read its message and line number; earlier structural errors may shift where the parser reports failure. Retest after the fix.

۴. worker_connections در http

نام directive درست است، ولی nginx -t می‌گوید is not allowed here. کدام فرض اشتباه بود؟

The directive name is correct, but nginx -t says is not allowed here. Which assumption was wrong?

پاسخ را ببینShow solution

اینکه نوشتن directive درست در هر block مجاز باشد. باید آن را در events قرار داد؛ parser آن را می‌شناسد ولی context را رد کرده است. بعد از جابه‌جایی، همان config باید تست شود.

The assumption that a correctly spelled directive is valid in every block. Move it into events; the parser recognized the name but rejected its context. Test the same config after moving it.

۵. هم‌سطح‌های main5. Siblings under main

جایگاه معمول events و http نسبت به هم چیست؟ یکی را داخل دیگری می‌گذاری؟

How are events and http normally placed relative to each other? Does one go inside the other?

پاسخ را ببینShow solution

هر دو زیرمجموعهٔ main و هم‌سطح‌اند؛ events داخل http نیست. درخت contextها را رسم کن؛ نام بلوک‌ها خودبه‌خود رابطهٔ والد-فرزندی نمی‌سازد.

Both are children of main and normally siblings; events is not nested inside http. Draw the context tree—block names do not create parent-child relationships by themselves.

۶. نظر دربارهٔ comment6. What does the comment do?

در فایل نوشته شده # root /srv/www;. آیا Nginx این root را اعمال می‌کند؟

The file contains # root /srv/www;. Does Nginx apply this root?

پاسخ را ببینShow solution

نه؛ خط comment برای خواننده است، نه یک directive فعال. اگر صفحه از مسیر دیگری می‌آید، config بارشده را بررسی کن؛ commentشده‌بودن این خط نمی‌تواند root مؤثر را تعیین کند.

No. A comment is for readers, not an active directive. If the page comes from another path, inspect the loaded configuration; this commented line cannot determine the effective root.

۷. include بدون جادو7. Include without magic

یک فایل shared.conf داخل /etc/nginx وجود دارد، ولی هیچ‌جا include نشده. آیا directiveهایش فعال‌اند؟

A shared.conf file exists under /etc/nginx, but nothing includes it. Are its directives active?

پاسخ را ببینShow solution

صرف وجود فایل کافی نیست؛ باید در درخت بارشده باشد. nginx -T را با config مربوط اجرا کن و دنبال سرآیند filename بگرد. اگر نمی‌آید، خط include یا glob آن را بررسی کن.

The file’s presence is not enough; it must be part of the loaded tree. Run nginx -T for that configuration and look for its filename header. If absent, inspect the include line or glob.

۸. context از کجا می‌آید؟8. Where does the context come from?

فایل shared.conf فقط default_type text/plain; دارد. اگر از بدنهٔ http include شود، در کدام context خوانده می‌شود؟

A shared.conf file contains only default_type text/plain;. If included inside the http body, in which context is it read?

پاسخ را ببینShow solution

در همان context http، چون include مرز یا محدودهٔ تازه نمی‌سازد؛ محتوا در نقطهٔ includeشده قرار می‌گیرد. پس همان فایل اگر داخل events وارد شود، context نامعتبر خواهد بود.

It is read in the http context because include creates no new boundary or scope; its contents are inserted where the include appears. Put the same file in events and the context is invalid.

۹. متغیر ثابت است؟9. Is the variable fixed?

دو درخواست با URIهای /a و /b به return 200 "$uri"; می‌رسند. کدام خروجی‌ها را پیش‌بینی می‌کنی؟

Two requests with URIs /a and /b reach return 200 "$uri";. What responses do you predict?

پاسخ را ببینShow solution

به‌ترتیب مقدارهای متناظر با URI جاری، یعنی /a و /b. متغیر در پردازش هر درخواست ارزیابی می‌شود؛ مقدار ثابتِ زمان parse نیست. این پیش‌بینی را با دو curl روی config آزمایشی ثابت می‌کنم.

They return the corresponding current URI values, /a and /b. The variable is evaluated while each request is processed, not fixed at parse time. Verify the prediction with two curl requests against the test config.

۱۰. وراثت root10. root inheritance

در server مقدار root /srv/site; هست. داخل location /images/ مقدار root تعریف نشده. آیا حتماً خطای «root پیدا نشد» می‌گیری؟

The server sets root /srv/site;. The location /images/ does not define root. Must you get a “root not found” error?

پاسخ را ببینShow solution

نه؛ root از سطح بالاتر به ارث می‌رسد اگر در context فعلی تعریف نشده باشد. اما مسیر ساخته‌شده از ترکیب root و URI است؛ وجود و permission خود فایل را باید جدا بررسی کرد.

No. root is inherited when the current context does not define it. But the resulting file path is built from the root and URI; file existence and permissions still need separate checks.

۱۱. چرا header والد ناپدید شد؟11. Why did the parent header disappear?

در http یک add_header X-Base yes; هست. در server هم add_header X-Site yes; گذاشتی. پاسخ از server می‌آید ولی فقط X-Site را دارد. چرا؟

The http level defines add_header X-Base yes;. The server defines add_header X-Site yes;. The response comes from this server but has only X-Site. Why?

پاسخ را ببینShow solution

طبق قاعدهٔ استاندارد، directiveهای add_header از سطح والد فقط وقتی ارث می‌رسند که در سطح فعلی هیچ add_header نباشد. این رفتار را با وراثت سادهٔ root یکی نگیر و نسخهٔ Nginx/وجود add_header_inherit را هم بررسی کن.

Under the standard rule, parent add_header directives are inherited only if the current level defines none. Do not confuse this with simple root inheritance, and check the Nginx version and whether add_header_inherit is configured.

۱۲. directive ناشناخته یا context غلط؟12. Unknown directive or wrong context?

خروجی می‌گوید unknown directive "worker_conections". در مقابل، خروجی دیگر می‌گوید "worker_connections" directive is not allowed here. این‌ها چه تشخیص‌های متفاوتی‌اند؟

One message says unknown directive "worker_conections"; another says "worker_connections" directive is not allowed here. What different diagnoses do these suggest?

پاسخ را ببینShow solution

اولی به غلط املایی، build یا module مربوط مشکوک است؛ دومی نام درست را شناخته ولی جایگاهش را رد کرده. متن کامل و محل گزارش را بخوان؛ صرف دیدن کلمهٔ “directive” کافی نیست.

The first suggests a typo or a build/module issue; the second recognizes the correct name but rejects its placement. Read the full message and location; spotting the word “directive” is not enough.

۱۳. parse موفق یعنی چه؟13. What does a successful parse prove?

nginx -t موفق است، ولی درخواست curl پاسخ مورد انتظار را ندارد. کدام ادعا را نباید مطرح کنی؟

nginx -t succeeds, but curl does not return the expected response. Which claim must you avoid?

پاسخ را ببینShow solution

نباید بگویم «پس سایت درست تنظیم شده». آزمون، syntax و دسترسی فایل‌های ارجاع‌شده را می‌سنجد، نه نتیجهٔ زمان اجرای هر درخواست را. بعد باید درخواست واقعی، کد وضعیت، بدنهٔ پاسخ و context مؤثر را بررسی کنم.

Do not claim that the site is correctly configured just because syntax passes. The test checks parsing and referenced files, not the runtime result of each request. Inspect the actual request, status, body, and effective context.

۱۴. include چند فایل14. Include multiple files

در config خط include /etc/nginx/conf.d/*.conf; آمده. آیا هر فایلی در آن پوشه، در هر context قابل استفاده است؟

The config contains include /etc/nginx/conf.d/*.conf;. Is every file in that directory valid in any context?

پاسخ را ببینShow solution

نه. glob تعیین می‌کند چه فایل‌هایی match شوند، نه اینکه context آن‌ها آزاد باشد. همهٔ fragmentها باید در نقطهٔ include قانونی باشند؛ خروجی nginx -T و nginx -t را برای مسیر و خط بررسی کن.

No. The glob selects matching files; it does not make their context unrestricted. Each fragment must be valid at the include point. Use nginx -T and nginx -t to inspect paths and errors.

۱۵. انتخاب روش بازاستفاده15. Choose a reuse mechanism

می‌خواهی یک خط ثابت را در چند config تکرار نکنی. از متغیرهایی مثل $shared_text استفاده می‌کنی یا include؟ چرا؟

You want to avoid repeating a static line across configs. Would you use a variable such as $shared_text or an include? Why?

پاسخ را ببینShow solution

برای متن ثابت، include یا ابزار تولید config مناسب‌تر است. متغیرهای Nginx معمولاً هنگام پردازش درخواست ارزیابی می‌شوند و macro جایگزین متن نیستند؛ ضمن اینکه فقط directiveهای مشخص از متغیر پشتیبانی می‌کنند.

For static text, an include or external config generator is more suitable. Nginx variables are generally evaluated during request processing and are not text macros; also, only certain directives support variables.

۱۶. آکولاد زودبسته16. An early closing brace

فایل خطای syntax می‌دهد و directiveای را نشان می‌دهد که به نظرت در context مجاز خودش است. یک آکولاد قبل‌تر از آن بسته شده. مسیر تشخیصت چیست؟

The file has a syntax error at a directive you believe is in a valid context. An earlier brace may have closed a block too soon. How do you diagnose it?

پاسخ را ببینShow solution

از خط گزارش‌شده به عقب می‌روم و باز و بسته‌شدن بلوک‌ها را می‌شمارم؛ فایل‌های includeشده را هم با nginx -T وارد بررسی می‌کنم. یک diff کوچک یا تورفتگی کمک دیداری است، اما تورفتگی خودش محدوده نمی‌سازد؛ آکولادها می‌سازند.

Work backward from the reported line and match opening and closing braces, including included files shown by nginx -T. A small diff or indentation can help visually, but indentation does not create scope—the braces do.

۱۷. دو درخواست، دو مقدار17. Two requests, two values

پس از reload configای که در پاسخ $uri را چاپ می‌کند، برای مسیرهای /one و /two یک متن پاسخ می‌بینی. اول کدام دو فرض را بررسی می‌کنی؟

After reloading a config that returns $uri, you see one response text for both /one and /two. Which two assumptions do you check first?

پاسخ را ببینShow solution

آیا config تازه واقعاً reload شده و آیا هر دو درخواست به همان listener و server می‌رسند؟ بعد بدنهٔ پاسخ و مسیر فرستاده‌شده را جداگانه ثبت می‌کنم. متغیر برای هر درخواست محاسبه می‌شود؛ اگر مقدار ثابت است، شاید اصلاً نسخهٔ config یا endpoint موردنظر را نمی‌آزمایم.

Did the new config actually reload, and do both requests reach the same listener and server? Record the response body and requested path separately. The variable is per request; a repeated value may mean you are not testing the intended config or endpoint.

۱۸. یک config را مرور کن18. Review a configuration

همکارت configای فرستاده که include دارد، چند context تو‌در‌تو و چند مقدار add_header. سه پرسش مشخصی بنویس که قبل از تأیید از او می‌پرسی.

A teammate sends a config with an include, nested contexts, and several add_header values. Write three concrete questions you would ask before approving it.

پاسخ را ببینShow solution

۱) هر directive در context مجازش هست؟ ۲) فایل include دقیقاً در چه نقطه‌ای وارد می‌شود و fragment در همان context معتبر است؟ ۳) اگر add_header در سطح پایین‌تر آمده، آیا header والد طبق قاعدهٔ نسخهٔ مورد استفاده باقی می‌ماند یا کنار می‌رود؟ بعد nginx -t/-T و یک درخواست واقعی را به‌عنوان مدرک می‌خواهم.

1) Is each directive in an allowed context? 2) Exactly where is the include inserted, and is its fragment valid there? 3) If a lower level defines add_header, do parent headers remain or disappear under the rule for this Nginx version? Then I would ask for nginx -t/-T and a real request as evidence.

مرجع سریع فصلQuick reference

اگر این را دیدی…If you see…این را بپرسAsk thisمدرک بعدیNext evidence
directive is not allowed hereکدام context باز است؟Which context is currently open?nginx -t و درخت آکولادها
فایل هست، ولی اثر نداردThe file exists but has no effectآیا از درخت include بار می‌شود؟Is it loaded through an include?nginx -T
مقدار والد ناپدید شدA parent value disappearedقاعدهٔ همین directive چیست؟What is this directive’s inheritance rule?مستندات همان directive و نسخهThat directive’s docs and version
متغیر مقدار دیگری داردA variable has another valueدر زمان parse است یا برای هر درخواست؟Is it parse-time or per-request?یک درخواست کنترل‌شده و body پاسخOne controlled request and its response body

برای مرجع رسمی: ساختار فایل Nginx، directiveِ include، رفتار root، وراثت add_header و پرسش رسمی دربارهٔ متغیرها. نسخهٔ نصب‌شده و متن پیکربندی خودت را مبنا بگیر.

Official references: Nginx configuration structure, the include directive, root behavior, add_header inheritance, and the official FAQ about variables. Use your installed version and actual configuration as the source of truth.

حالا چند server روی یک Nginx؟Now, multiple servers on one Nginx?

حالا می‌توانی یک فایل را بخوانی، مرز blockها را پیدا کنی و بفهمی دستور در کدام context معنا دارد. قدم بعدی این است: اگر چند server روی یک IP و یک پورت باشند، Nginx چطور تصمیم می‌گیرد درخواست به کدام یکی برسد؟ فصل بعد با virtual host و server_name همین سؤال را دنبال می‌کند؛ هنوز قرار نیست جوابش را از روی ترتیب فایل‌ها حدس بزنیم.

You can now read a file, find block boundaries, and identify the context in which a directive makes sense. Next: if several server blocks share one IP and port, how does Nginx decide which one receives a request? The next chapter follows that question through virtual hosts and server_name; we will not guess from file order.