Nginx چیست؟ درگاه عمومی و پراکسی معکوس برای بکاند Node.js
در این درس ۶ بخش
تا اینجا برنامه با systemd روی اوبونتو اجرا میشود و از داخل همان سرور، نشانی 127.0.0.1:3000/health پاسخ میدهد. این یک نقطهٔ کنترل مهم است: پیش از افزودن هر لایهٔ تازه، باید بدانیم لایهٔ قبلی سالم است. هدف این درس ساخت یک درگاه HTTP برای درخواستهای بیرونی است؛ درگاه فقط دو مسیر خواندنی را به برنامه میرساند. اگر درخواست محلی هنوز شکست میخورد، این درس را با نصب دوبارهٔ ابزارها ادامه ندهید؛ به درس سرویس برگردید و خطای برنامه یا مجوز را حل کنید. محیط فرمانها در این درس Bash داخل اتصال SSH به ماشین آزمایشی Ubuntu Server 24.04 است، نه PowerShell ویندوز. فایلهای تنظیمات را با کاربر مدیریتی و sudo مینویسیم، اما برنامهٔ Node همچنان با کاربر محدود darsnode اجرا میشود.
پیشنیاز را با زبان دقیق مرور کنیم: یک «فرایند» نمونهٔ در حال اجرای برنامه است؛ «پورت» شمارهای در ارتباط شبکه است که سیستم عامل برای رساندن اتصال به سرویس شنونده استفاده میکند؛ و loopback نشانی ارتباط خود دستگاه با خودش است. وقتی Node فقط به 127.0.0.1 متصل است، بیرون از دستگاه نمیتوان مستقیم به همان سوکت وصل شد. برای مرور مسئولیت برنامه و پاسخ #json، درس «از مرورگر تا سرور» دورهٔ قبل را باز کنید:
https://www.darsavand.com/courses/nodejs-windows-backend-start?lesson=12cd93b9-e8a7-44fb-b60b-928a8495a617
اگر تفاوت IP، DNS و پورت هنوز مبهم است، این درس مقدماتی لینوکس را بخوانید:
https://www.darsavand.com/courses/linux-server-from-zero?lesson=6f7aa53e-7468-4f60-9ef1-8cb867907928
پراکسی معکوس چه کاری انجام میدهد؟
Nginx وبسروری است که در این سناریو نقش پراکسی معکوس دارد: درخواست بیرونی را میگیرد، مسیر مجاز را انتخاب میکند، به برنامهٔ پشت خود میفرستد و پاسخ آن را به کلاینت برمیگرداند. مانند پذیرش یک ساختمان است که مراجعهکننده را به اتاق مناسب هدایت میکند؛ اما این تشبیه به معنی کنترل هویت خودکار نیست. پراکسیکردن درخواست، کاربر را احراز هویت نمیکند. در معماری ما مرورگر به پورت ۸۰ و بعداً ۴۴۳ میرسد؛ Nginx از روی همان سرور به پورت ۳۰۰۰ وصل میشود. برنامه برای استفاده از پورت عمومی نیازی به اجرا با root ندارد. تفکیک این دو کار، محل تنظیم HTTPS، ثبت درخواست و مسیرها را روشن میکند. در سامانههای بزرگ، لایههای دیگری هم وجود دارند؛ فعلاً یک پراکسی مستقیم و یک فرایند برنامه داریم تا رفتار هر بخش قابل مشاهده بماند.
curl --fail --show-error http://127.0.0.1:3000/health
sudo apt update
sudo apt install nginx nano
nginx -v
sudo systemctl status nginx --no-pager
sudo ss -lntp '( sport = :80 or sport = :443 )'دستور اول باید پاسخ سالم برنامه را بدهد؛ اگر اتصال رد شد، پیش از نصب Nginx همان مشکل را حل کنید. apt update فهرست بستهها را تازه میکند و apt install بستهٔ Nginx و ویرایشگر nano را نصب میکند. وضعیت سرویس باید running باشد و ss نشان دهد چه فرایندی روی پورت عمومی گوش میدهد. اگر پورت ۸۰ قبلاً در اختیار برنامهٔ دیگری است، آن فرایند را بیبررسی متوقف نکنید؛ فرض این دوره یک ماشین تازه و اختصاصی است. صفحهٔ پیشفرض Welcome to nginx فقط ثابت میکند وبسرور جواب داده؛ موفقیت بکاند را ثابت نمیکند. نسخهٔ بسته را ثبت کنید و از کپیکردن تنظیمات مربوط به نسخههای بسیار جدیدتر بدون بررسی قابلیتها خودداری کنید.
دو فایل تنظیمات؛ چرا محل نوشتن مهم است؟
Nginx تنظیمات را در بخشهایی به نام context میخواند. context یعنی محدودهای که یک دستور در آن معتبر است؛ log_format باید در محدودهٔ http باشد، ولی server یک میزبان وب و location یک قاعدهٔ انتخاب مسیر را توصیف میکند. بستهٔ استاندارد اوبونتو فایلهای conf.d را داخل http میخواند و sites-enabled را برای میزبانهای فعال استفاده میکند. اول با sudo nano /etc/nginx/nginx.conf محل این دو include را فقط مشاهده کنید و بدون تغییر از ویرایشگر بیرون بیایید. اگر فایل شما چنین ساختاری ندارد، مسیر سفارشی را به زور با این آموزش مخلوط نکنید. سپس با دستور بعد فایل تازهٔ قالب لاگ را باز کنید؛ شرط test مانع بازنویسی اتفاقی یک فایل موجود میشود. در nano متن بلوک بعد را وارد کنید، با Ctrl+O و Enter ذخیره و با Ctrl+X خارج شوید.
sudo test ! -e /etc/nginx/conf.d/darsavand-trace.conf && sudo nano /etc/nginx/conf.d/darsavand-trace.conflog_format dars_node escape=json
'{"time":"$time_iso8601","request_id":"$request_id",'
'"status":$status,"method":"$request_method","uri":"$uri",'
'"upstream":"$upstream_status","duration":$request_time}';این فایل قالب هر خط لاگ را تعریف میکند؛ هنوز سرویس تازه را فعال نکرده است. escape=json نویسههای خاص را در مقدارهای متنی برای ساخت یک خط JSON معتبر فراردهی میکند. request_id شناسهٔ همان درخواست در Nginx است و برای تطبیق پاسخ و خط لاگ استفاده خواهد شد. uri مسیر است و عمداً query string را در این قالب نمینویسیم؛ رمز، توکن یا دادهٔ شخصی را در نشانی قرار ندهید. این انتخاب، ثبت اطلاعات حساس در همهٔ لاگهای ممکن دستگاه را تضمین نمیکند؛ error log و تنظیمات سایر سایتها هم سیاست خود را دارند. اگر فایل از قبل وجود داشت، فرمان ویرایش اجرا نمیشود؛ اول محتوا و علت وجود آن را بررسی کنید، نه اینکه شرط محافظ را حذف کنید.
ساخت میزبان و تعریف دقیق مسیرهای عمومی
sudo test ! -e /etc/nginx/sites-available/darsavand-node && sudo nano /etc/nginx/sites-available/darsavand-nodeserver {
listen 80;
listen [::]:80;
server_name api.example.com;
access_log /var/log/nginx/darsavand-node.access.log dars_node;
error_log /var/log/nginx/darsavand-node.error.log warn;
add_header X-Request-Id $request_id always;
client_max_body_size 8k;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-Id $request_id;
proxy_connect_timeout 3s;
proxy_read_timeout 10s;
location = /health {
limit_except GET { deny all; }
proxy_pass http://127.0.0.1:3000/health;
}
location = /api/tasks {
limit_except GET { deny all; }
proxy_pass http://127.0.0.1:3000/tasks;
}
location / {
return 404;
}
}api.example.com جاینگهدار است: پیش از ذخیره، آن را با زیردامنهٔ واقعی تحت اختیار خودتان عوض کنید؛ مثلاً نامی که در پنل DNS خود ساختهاید. example.com متعلق به شما نیست و برای مثال مستندات نگهداری میشود. server_name به انتخاب میزبان هنگام دریافت درخواست کمک میکند؛ خودش گذرواژه یا کنترل دسترسی نیست. علامت مساوی در location یعنی فقط همان مسیر دقیق انتخاب شود. بنابراین /api/tasks به /tasks برنامه نگاشت میشود، ولی /api/tasks/ یا /api/tasks/شناسه مجاز نشده است. proxy_pass نشانی مقصد داخلی است؛ اینجا نام یک فایل روی دیسک نیست. تعریف #function در کد قبلی، رفتار پاسخ را تعیین میکرد؛ تنظیم Nginx تعیین میکند کدام درخواست اصلاً فرصت رسیدن به آن رفتار را دارد. ارتباط این دو لایه را جدا توضیح دهید تا هنگام خطا سراغ فایل اشتباه نروید.
limit_except GET همراه deny all مسیرهای نوشتنی را از اینترنت میبندد. Nginx بهطور ضمنی HEAD را هم کنار GET مجاز میداند، ولی برنامهٔ فعلی HEAD را پیاده نکرده و ۴۰۵ میدهد؛ پس برای آزمایش سلامت از GET استفاده کنید، نه curl -I. این سرویس، سامانهٔ خصوصی مدیریت کار نیست: خواندن /api/tasks عمومی است، بنابراین فقط دادهٔ ساختگی وارد کنید. POST و PATCH را فقط در اتصال داخلی یا تونل SSH درسهای قبلی تمرین میکنیم. برای محصول واقعی، علاوه بر #validation-contracts، احراز هویت، مجوز هر کاربر و پایگاه دادهٔ بادوام لازماند. محدودیت روش HTTP جای این طراحی را نمیگیرد. یک Map حافظهای با توقف برنامه پاک میشود؛ اینجا عمدی است و قبل از واردکردن هر داده باید آن را بدانید.
فعالسازی کنترلشده و آزمون پیش از تغییر DNS
sudo test ! -e /etc/nginx/sites-enabled/darsavand-node && sudo ln -s /etc/nginx/sites-available/darsavand-node /etc/nginx/sites-enabled/darsavand-node
sudo nginx -t && sudo systemctl reload nginx
curl -i -H 'Host: api.example.com' http://127.0.0.1/health
curl -i -H 'Host: api.example.com' http://127.0.0.1/api/tasksدر دو دستور curl، Host را نیز به همان نام واقعی داخل server_name تغییر دهید. این آزمایش روی خود اوبونتو است؛ هنوز به انتشار رکورد DNS یا بازبودن فایروال اینترنت وابسته نیست. symlink یک اشاره به فایل موجود است؛ فعالسازی با کپی مستقل فایل فرق دارد و ویرایشهای بعدی همان منبع را تغییر میدهند. nginx -t باید موفقیت نحو و امکان بازکردن منابع لازم را گزارش کند. علامت && اجازه میدهد reload فقط پس از موفقیت این آزمون اجرا شود. برای /health انتظار وضعیت ۲۰۰ و بدنهٔ سالم و برای /api/tasks انتظار ۲۰۰ و شیء دارای tasks داریم؛ فهرست میتواند خالی باشد. پاسخ، سرآیند X-Request-Id هم دارد. این شناسه را یادداشت کنید. موفقیت nginx -t وجود برنامهٔ Node را ثابت نمیکند؛ آزمایش HTTP، گام جداگانه و ضروری بعدی است.
curl -i -X POST -H 'Host: api.example.com' -H 'Content-Type: application/json' --data '{"title":"training only"}' http://127.0.0.1/api/tasks
curl -i -H 'Host: api.example.com' http://127.0.0.1/not-a-route
curl -I -H 'Host: api.example.com' http://127.0.0.1/healthبرای دادهٔ کوچک این آزمایش، POST باید ۴۰۳ بدهد و درخواست به مدل ساخت کار نرسد؛ مسیر ناشناخته ۴۰۴ و HEAD پاسخ ۴۰۵ برنامه را نشان میدهد. درخواست بسیار بزرگ ممکن است زودتر با ۴۱۳ توسط Nginx رد شود و این تناقض نیست: نخستین لایهٔ ردکننده وضعیت را تعیین میکند. اگر ۵۰۲ میبینید، Nginx نتوانسته پاسخ قابل استفاده از upstream بگیرد؛ اول curl مستقیم به پورت ۳۰۰۰ و سپس وضعیت systemd را بررسی کنید. ۴۰۴ صفحهٔ دیگری میتواند نتیجهٔ Host اشتباه یا فعالنشدن میزبان باشد. ۴۰۳ عمدی نوشتن را با شکست عمومی سرویس اشتباه نگیرید. برای فهم تفاوت شکست ارتباط و پاسخ ناموفق HTTP، درس خطایابی Node دورهٔ ویندوز را مرور کنید:
https://www.darsavand.com/courses/nodejs-windows-backend-start?lesson=7f46ca6f-c14d-45dd-b9b9-2ef88ae773aa
هدرها، لاگ و یک قرارداد قابل تحویل
proxy_set_header فیلدهای ارسالی به برنامه را تعیین میکند. در معماری تکپراکسی ما، X-Forwarded-For را از نشانی اتصال مستقیم میسازیم و مقدار دلخواه فرستنده را بهعنوان هویت معتبر قبول نمیکنیم. اگر بعداً CDN یا پراکسی دیگری اضافه شد، زنجیرهٔ اعتماد باید دوباره طراحی شود. هیچکدام از این هدرها جای مجوز کاربر را نمیگیرند. proxy_read_timeout هم زمان انتظار مربوط به دریافت از upstream است، نه تضمین پایان کل عملیات تجاری در ده ثانیه. برای مشاهده، sudo tail -n 10 /var/log/nginx/darsavand-node.access.log را بخوانید: status پاسخ بیرونی است و upstream وضعیت بخش پشت Nginx. در ۴۰۳ مسدودشده، ممکن است upstream خط تیره باشد چون برنامه اصلاً فراخوانی نشده است. ارتباط این خط با #debugging-console را درک کنید: همانطور که چاپ در برنامه شاهد است، لاگ پراکسی هم فقط شاهد یک لایهٔ مشخص است.
تمرین با پاسخ؛ اگر هر لایه را جدا بسنجیم چه میشود؟
تمرین: در گزارشی چهار ردیف بنویسید: GET سلامت مستقیم به ۳۰۰۰؛ GET سلامت از Nginx با Host صحیح؛ POST عمومی به /api/tasks؛ و یک مسیر ناشناخته. برای هر ردیف انتظار، نتیجهٔ واقعی و لایهٔ پاسخدهنده را ثبت کنید. پاسخ راهنما بهترتیب ۲۰۰ از برنامه، ۲۰۰ از مسیر پراکسی، ۴۰۳ از محدودیت Nginx و ۴۰۴ از قاعدهٔ مسیر است. اگر مستقیم موفق و پراکسی ۵۰۲ بود، فرضیهٔ «کل Node نصب نیست» با شاهد اول رد میشود؛ تنظیم مقصد، فایل فعال و error log را بررسی کنید. برای کنارگذاشتن موقت همین میزبان تازه، ابتدا با readlink مقصد پیوند را تأیید و فقط پیوند مخصوص این تمرین را بردارید؛ فایل منبع را نگه دارید و قبل از reload دوباره nginx -t بزنید. فایل پیشفرض یا سایت دیگران را حذف نکنید. معیار تحویل این است که بتوانید مسیر درخواست و علت ممنوعبودن نوشتن عمومی را با زبان خودتان شرح دهید، نه اینکه فقط صفحهٔ سبز مرورگر را نشان دهید.
منابع رسمی انگلیسی برای بررسی جزئیات:
Ubuntu — Install nginx: https://ubuntu.com/server/docs/how-to/web-services/install-nginx/
Nginx — Beginner’s guide: https://nginx.org/en/docs/beginners_guide.html
Nginx — Proxy module: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
Nginx — Core module and limit_except: https://nginx.org/en/docs/http/ngx_http_core_module.html
Nginx — Log module: https://nginx.org/en/docs/http/ngx_http_log_module.html
Nginx — Response headers: https://nginx.org/en/docs/http/ngx_http_headers_module.html
IANA — Example domains: https://www.iana.org/help/example-domains
متن فارسی، سناریوها و پیکربندی آموزشی: تألیف مستقل تحریریه درسآوند.
متن درسهای عمومی را بدون ورود بخوانید.