SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

HTTP چیست؟ راهنمای جامع برای مدیران سرور

در این راهنما با ساختار HTTP، متدها، کدهای وضعیت و هدرهای کلیدی آشنا شوید. بررسی دقیق نحوه ثبت درخواست‌ها در access log سرورهای nginx و نقش TLS و HTTP/3 در امنیت و سرعت.

HTTP چیست؟

HTTP (مخفف hypertext transfer protocol) مجموعه‌ای از قوانین است که کلاینت و وب‌سرور برای درخواست یک منبع و ارسال پاسخ از آن استفاده می‌کنند. کلاینت یک درخواست ارسال می‌کند: متدی مانند GET، مسیری مانند /pricing، نسخه پروتکل، فهرستی از هدرها و گاهی اوقات یک بدنه (body). سرور با یک کد وضعیت مانند 200 پاسخ می‌دهد که به دنبال آن هدرهای خود سرور و معمولاً یک بدنه قرار می‌گیرد. هر بار مشاهده صفحه و هر فراخوانی API (مخفف application programming interface) روی سرور شما، همین تبادل است که تکرار می‌شود.

پروتکل HTTP به‌خودی‌خود فاقد وضعیت (stateless) است. سرور به خاطر نمی‌آورد که شما یک ثانیه پیش چه چیزی درخواست کرده‌اید؛ بنابراین هر چیزی که رفتاری شبیه به حافظه داشته باشد، مانند نشست ورود (login session)، در هر درخواست درون یک هدر حمل می‌شود. همین ویژگی، بسیاری از موارد بعدی را توضیح می‌دهد: کشینگ کاملاً مبتنی بر هدر است و یک load balancer می‌تواند درخواست بعدی شما را بدون ایجاد اختلال، به یک backend متفاوت ارسال کند.

تمام موارد زیر نشان‌دهنده چگونگی نمایش این مدل از سمت سرور، در access log و در پیکربندی nginx شما است.

یک درخواست و پاسخ خام، همراه با توضیحات

در اینجا یک درخواست کامل HTTP/1.1 آورده شده است. یک خط خالی پایان هدرها را مشخص می‌کند و هر چیزی بعد از آن خط، بدنه (body) محسوب می‌شود. یک GET معمولاً بدنه ندارد.

GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzip
  • GET متد است که مشخص می‌کند چه کاری باید انجام شود. GET داده‌ها را می‌خواند، POST داده‌ها را ارسال می‌کند، PUT جایگزین می‌کند، DELETE حذف می‌کند، و HEAD فقط هدرهای یک GET را بدون بدنه درخواست می‌کند.
  • /pricing مسیر (path) است. نام میزبان (hostname) بخشی از خط درخواست نیست و به همین دلیل هدر بعدی وجود دارد.
  • HTTP/1.1 نسخه پروتکلی است که کلاینت از آن استفاده می‌کند.
  • Host: example.com نام سایتی است که کلاینت می‌خواهد به آن دسترسی داشته باشد. HTTP/1.1 به آن نیاز دارد، بنابراین nginx به درخواستی که فاقد آن باشد، پاسخ 400 Bad Request می‌دهد.
  • بقیه موارد، تنظیمات ترجیحی هستند. Accept-Encoding: gzip می‌گوید کلاینت می‌تواند داده‌های فشرده را باز کند، بنابراین سرور اجازه دارد بدنه را فشرده کند.

پاسخ نیز ساختاری مشابه دارد و در بالای آن یک خط وضعیت (status line) قرار گرفته است.

HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300

<!doctype html>...
  • 200 OK کد وضعیت به همراه عبارت توصیفی آن است. کد وضعیت اهمیت دارد، اما عبارت توصیفی صرفاً جنبه نمایشی دارد و کلاینت‌ها آن را نادیده می‌گیرند.
  • Content-Type به کلاینت می‌گوید که با بایت‌های بعدی چگونه رفتار کند.
  • Content-Length اندازه بدنه به بایت است تا کلاینت بداند بدنه کجا به پایان می‌رسد. وقتی اندازه از قبل مشخص نباشد، سرور به جای آن Transfer-Encoding: chunked ارسال می‌کند و پایان را با یک قطعه (chunk) با طول صفر مشخص می‌کند.
  • Cache-Control به مرورگر و هر کش (cache) میانی می‌گوید که تا چه مدت می‌توانند این پاسخ را نگه دارند.
  • خط خالی بعد از هدرها، آن‌ها را از بدنه جدا می‌کند؛ این قاعده در هر دو جهت (درخواست و پاسخ) صادق است.

نام هدرها به حروف بزرگ و کوچک حساس نیست و هر خط با یک کاراکتر بازگشت به ابتدای خط (carriage return) و به دنبال آن یک خط جدید (line feed) پایان می‌یابد، نه فقط یک خط جدید ساده. شما این‌ها را به صورت دستی تایپ نخواهید کرد، اما در تحلیل بسته‌های شبکه (packet capture) با آن‌ها مواجه می‌شوید.

برای مشاهده یک نمونه واقعی، دستور زیر را روی سایتی که مالک آن هستید اجرا کنید:

curl -sS -o /dev/null -D - https://example.com/

-D - هدرهای پاسخ را در ترمینال شما چاپ می‌کند و -o /dev/null بدنه را دور می‌ریزد. این روش را به curl -I ترجیح دهید، زیرا -I یک درخواست HEAD ارسال می‌کند. یک سرور اپلیکیشن که با HEAD متفاوت از GET رفتار می‌کند (که بسیاری از آن‌ها چنین هستند)، در این حالت هدرهایی را به شما نشان می‌دهد که هیچ مرورگری آن‌ها را دریافت نمی‌کند. curl -v هر دو سمت را چاپ می‌کند، به طوری که خطوط درخواست با > و خطوط پاسخ با < مشخص شده‌اند.

ساختار خط درخواست در لاگ دسترسی nginx شما

نرم‌افزار nginx دارای یک فرمت لاگ پیش‌فرض به نام combined است که تعریف آن به شرح زیر است:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

یک خط تولید شده توسط این فرمت:

203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"
  • مقدار 203.0.113.45 همان $remote_addr است؛ یعنی آدرسی که اتصال TCP (پروتکل کنترل انتقال) را برقرار کرده است. در پشت یک پروکسی، این مقدار نشان‌دهنده خودِ پروکسی است، نه بازدیدکننده اصلی.
  • اولین - یک جای‌نگهدار ثابت است. دومی مربوط به $remote_user است که تنها در صورت استفاده از احراز هویت پایه HTTP پر می‌شود.
  • مقدار "GET /pricing HTTP/1.1" همان $request است؛ یعنی خط درخواست که دقیقاً همان‌طور که دریافت شده، کپی می‌شود.
  • مقدار 200 وضعیتی است که سرور شما بازگردانده است، نه وضعیتی که بازدیدکننده مشاهده کرده است.
  • مقدار 5310 همان $body_bytes_sent است که فقط شامل بدنه (body) می‌شود. هدرهای پاسخ در این محاسبه لحاظ نمی‌شوند، بنابراین این عدد همیشه از تعداد بایت‌های واقعی ارسال‌شده کمتر است.
  • دو فیلد آخر که داخل کوتیشن قرار دارند، Referer و User-Agent هستند. هر دو از سمت کلاینت ارسال می‌شوند، بنابراین می‌توانند حاوی هر مقداری باشند.

از آنجا که $request عیناً کپی می‌شود، داده‌های نامعتبر نیز عیناً ثبت می‌شوند. کلاینتی که سعی می‌کند با پروتکل TLS (امنیت لایه انتقال) به پورت 80 شما که برای متن ساده (plaintext) است متصل شود، یک خط 400 ایجاد می‌کند که فیلد درخواست آن با بایت‌های escape شده‌ای مانند "\x16\x03\x01\x02\x00\x01" شروع می‌شود. مقدار \x16 نوع رکورد دست‌دادن (handshake) TLS است، بنابراین این بایت‌ها در واقع شروع یک ClientHello هستند و اصلاً یک خط درخواست محسوب نمی‌شوند. سرور شما به درستی عمل می‌کند؛ مشکل اینجاست که چیزی در حال ارسال ترافیک HTTPS به یک پورت HTTP است.

همچنین $server_protocol را به فرمت لاگ خود اضافه کنید. این پارامتر مقدار HTTP/1.1، HTTP/2.0 یا HTTP/3.0 را چاپ می‌کند و سریع‌ترین راه برای اثبات این است که تغییر پروتکل واقعاً اعمال شده است.

معنای کدهای وضعیت رایج هنگام بازگشت از سایت شما

رقم اول نشان‌دهنده کلاس کد است و این همان چیزی است که باید ابتدا به آن توجه کنید.

2xx به معنای موفقیت‌آمیز بودن است. 200 OK برای یک خواندن عادی. 201 Created پس از یک POST که چیزی ایجاد کرده است. 204 No Content برای موفقیت بدون محتوایی برای ارسال، که پاسخ معمول به یک DELETE است.

3xx به معنای ارجاع به جای دیگر است. 301 دائمی است و مرورگرها آن را به‌شدت کش می‌کنند، گاهی تا زمانی که کاربر پروفایل خود را پاک کند؛ بنابراین یک 301 که به نام میزبان اشتباه اشاره کند، به‌سختی قابل اصلاح است. در حالی که هنوز در حال تست کردن یک تغییر مسیر (redirect) هستید، از 302 استفاده کنید. 304 Not Modified یک موفقیت است، نه خطا: کلاینت یک If-None-Match حاوی یک ETag (برچسب موجودیت) ارسال کرده که شما هنوز آن را می‌شناسید، بنابراین شما فقط هدرها را بدون بدنه پاسخ دادید. لاگی پر از 304ها به این معنی است که کش کردن به‌درستی کار می‌کند.

4xx به معنای اشتباه بودن درخواست است. 400 Bad Request یعنی ورودی ناقص یا نادرست است. 401 Unauthorized در واقع به معنای احراز هویت‌نشده است و باید حاوی یک هدر WWW-Authenticate باشد که طرح احراز هویت را مشخص کند. 403 Forbidden یعنی درخواست فهمیده شده اما به هر حال رد شده است. 404 Not Found مسیری است که وجود ندارد. 405 Method Not Allowed مسیر درست با متد اشتباه است، که نتیجه یک POST به محل یک فایل استاتیک است. 413 یعنی بدنه درخواست بزرگتر از client_max_body_size در nginx است که مقدار پیش‌فرض آن 1 مگابایت است و لاگ خطا آن را با client intended to send too large body تایید می‌کند.

یک 403 روی یک فایل استاتیک تقریباً همیشه مربوط به سیستم فایل است تا قوانین HTTP. پیش از تغییر هر پیکربندی، /var/log/nginx/error.log را مطالعه کنید. open() "/srv/site/index.html" failed (13: Permission denied) یعنی کاربر worker در nginx نمی‌تواند فایل را بخواند، که اغلب به این دلیل است که یکی از دایرکتوری‌های والد، مجوز اجرای (execute) لازم برای سایر کاربران (others) را ندارد. directory index of "/srv/site/" is forbidden یعنی مسیر به دایرکتوری‌ای اشاره دارد که فایل index ندارد، در حالی که autoindex غیرفعال است.

5xx به معنای بروز خطا در سمت شماست. 500 یک خطای مدیریت‌نشده در برنامه شماست. 502 Bad Gateway یعنی nginx نتوانسته پاسخ قابل استفاده‌ای از upstream دریافت کند و لاگ خطا علت را مشخص می‌کند: connect() failed (111: Connection refused) while connecting to upstream یعنی هیچ سرویسی روی آدرس مشخص شده در proxy_pass گوش نمی‌دهد. 504 Gateway Timeout یعنی upstream اتصال را پذیرفته اما در مدت proxy_read_timeout (به‌صورت پیش‌فرض 60 ثانیه) پاسخی نداده است، که لاگ آن را با upstream timed out (110: Connection timed out) while reading response header from upstream ثبت می‌کند. 503 Service Unavailable یک امتناع عمدی است. توجه داشته باشید که محدودکننده نرخ (rate limiter) خودِ nginx کد 503 را برمی‌گرداند، زیرا limit_req_status به‌صورت پیش‌فرض روی 503 تنظیم شده است. اگر به دنبال 429 Too Many Requests در لاگ خود هستید و به جای آن 503 می‌بینید، دلیلش همین است. برای دریافت کد دقیق، limit_req_status 429; را تنظیم کنید.

هدرهایی که هنگام اجرای سرور اهمیت دارند

Host سایت مورد نظر را انتخاب می‌کند. یک آدرس IP می‌تواند صدها نام میزبان را سرویس‌دهی کند و nginx مقدار Host را با server_name تطبیق می‌دهد تا تصمیم بگیرد کدام بلوک server پاسخگو باشد. اگر هیچ موردی مطابقت نداشته باشد، nginx از سرور پیش‌فرض استفاده می‌کند؛ یعنی اولین بلوکی که روی آن آدرس و پورت گوش می‌دهد، مگر اینکه بلوک دیگری با default_server مشخص شده باشد. دریافت سایت اشتباه از یک virtual host جدید تقریباً همیشه به این دلیل است: نام مطابقت نداشته و درخواست به سرور پیش‌فرض ارجاع داده شده است. آن را بدون دستکاری DNS تست کنید:

curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/

User-Agent یک توصیف خودکار است که توسط کلاینت نوشته می‌شود و متنی آزاد است. هنگام خواندن لاگ‌ها از آن به عنوان یک راهنما استفاده کنید. هرگز از آن به عنوان ابزار کنترل استفاده نکنید، زیرا کلاینتی که بخواهد در مورد آن دروغ بگوید به سادگی این کار را انجام می‌دهد؛ بنابراین مسدود کردن یک scraper با User-Agent فقط موارد مودب را فیلتر می‌کند.

Content-Type تعیین می‌کند که بایت‌ها چگونه تفسیر شوند: application/json برای یک درخواست API و text/html; charset=utf-8 برای یک صفحه. nginx پسوندهای فایل را با /etc/nginx/mime.types به نوع‌های مختلف نگاشت می‌کند و فایل nginx.conf پیش‌فرض، مقدار default_type application/octet-stream; را تنظیم می‌کند؛ بنابراین فایلی با پسوندی که nginx نمی‌شناسد، به جای نمایش داده شدن، برای دانلود ارائه می‌شود. نشانه ظاهری این مشکل، صفحه‌ای است که بدون استایل بارگذاری می‌شود و کنسول مرورگر خطای Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type را چاپ می‌کند. MIME در اینجا مخفف Multipurpose Internet Mail Extensions است، همان طرح نام‌گذاری که این رشته‌های نوع از آن می‌آیند.

Cache-Control روشی است که با آن تمام کش‌های بین سرور خود و خواننده را کنترل می‌کنید. public, max-age=31536000, immutable برای دارایی‌هایی (assets) مناسب است که نام فایل آن‌ها حاوی یک content hash است، زیرا با تغییر محتوا، نام فایل نیز تغییر می‌کند. no-store برای هر چیزی که مختص کاربر است استفاده می‌شود، زیرا یک کش اشتراکی که صفحه ورود یک کاربر را نگه می‌دارد، آن را به نفر بعدی که همان URL را درخواست کند نشان خواهد داد. private تنظیم میانی است: مرورگر ممکن است آن را نگه دارد، اما کش اشتراکی اجازه ندارد.

X-Forwarded-For به این دلیل وجود دارد که پروکسی، بازدیدکننده را پنهان می‌کند. هنگامی که یک درخواست از یک reverse proxy عبور می‌کند، $remote_addr آدرس همان پروکسی است، بنابراین لاگ‌ها، موقعیت جغرافیایی و محدودیت نرخ (rate limiting) شما همگی فقط یک کلاینت را می‌بینند. پروکسی باید آدرس اصلی را به این صورت ارسال کند:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

سپس سرور دریافت‌کننده باید طوری تنظیم شود که به آن اعتماد کند و دقیقاً بداند به چه کسی اعتماد کند:

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

فقط محدوده‌هایی را لیست کنید که تحت کنترل شما هستند. X-Forwarded-For یک متن ساده است که هر کلاینتی می‌تواند ارسال کند، بنابراین set_real_ip_from 0.0.0.0/0; به بازدیدکننده اجازه می‌دهد آدرسی را که شما لاگ می‌کنید و آدرسی که محدودکننده نرخ شما می‌شمارد، انتخاب کند.

X-Forwarded-Proto از یک شکست خاص و بسیار رایج جلوگیری می‌کند. پروکسی شما TLS را خاتمه می‌دهد و درخواست را از طریق HTTP ساده به برنامه ارسال می‌کند. برنامه یک درخواست ساده می‌بیند، تصمیم می‌گیرد که بازدیدکننده باید روی HTTPS باشد و پاسخ 301 https://example.com/ را برمی‌گرداند. مرورگر آن را دنبال می‌کند، پروکسی دوباره TLS را خاتمه می‌دهد و دوباره HTTP ساده را ارسال می‌کند؛ این چرخه تا زمانی که مرورگر با خطای ERR_TOO_MANY_REDIRECTS تسلیم شود، تکرار می‌شود. ارسال X-Forwarded-Proto: https به برنامه می‌گوید که بازدیدکننده در حال حاضر روی HTTPS است، بنابراین برنامه از تغییر مسیر (redirect) دست برمی‌دارد.

مقایسه HTTP/1.1، HTTP/2 و HTTP/3: چه چیزی برای شما تغییر می‌کند

پروتکل HTTP/1.1 متنی است و در هر اتصال، تنها یک درخواست را مدیریت می‌کند. Connection: keep-alive اجازه می‌دهد درخواست بعدی از همان اتصال TCP استفاده مجدد کند که باعث صرفه‌جویی در هزینه راه‌اندازی می‌شود، اما پاسخ‌ها همچنان به همان ترتیبی که درخواست شده‌اند بازمی‌گردند. یک پاسخ کند، تمام درخواست‌های موجود در صف پشت سر خود را مسدود می‌کند. این پدیده head-of-line blocking نام دارد و مرورگرها با باز کردن چندین اتصال همزمان به یک نام میزبان، آن را دور می‌زنند.

پروتکل HTTP/2 همان متدها و کدهای وضعیت را حفظ کرده و ساختار فریم‌بندی را به باینری تغییر می‌دهد. چندین درخواست از یک اتصال به عنوان جریان‌های مستقل استفاده می‌کنند و متن تکراری هدرها فشرده می‌شود؛ این موضوع اهمیت دارد زیرا درخواست‌های مدرن حاوی حجم زیادی از هدر هستند. اتصال همچنان TCP است، بنابراین یک بسته گم‌شده، تمام جریان‌های آن اتصال را تا زمان ارسال مجدد متوقف می‌کند. مشکل head-of-line blocking از بین نرفته، بلکه از لایه HTTP به لایه انتقال منتقل شده است. قابلیت Server push بخشی از HTTP/2 بود که در عمل کنار گذاشته شد، زیرا کروم پشتیبانی از آن را در سال 2022 حذف کرد.

پروتکل HTTP/3 دوباره همان معناشناسی را حفظ کرده و TCP را با QUIC جایگزین می‌کند؛ یک پروتکل انتقال که بر پایه UDP (پروتکل دیتاگرام کاربر) ساخته شده است. جریان‌های QUIC در تمام سطوح مستقل هستند، بنابراین یک بسته گم‌شده فقط همان جریانی را متوقف می‌کند که به آن تعلق داشته است. پروتکل TLS 1.3 به جای اینکه روی لایه دیگری قرار گیرد، در دست‌دادن (handshake) پروتکل QUIC ادغام شده است، بنابراین اتصال جدید به رفت‌وبرگشت‌های کمتری نیاز دارد. دو نتیجه عملی حاصل می‌شود: پورت UDP 443 باید در تمام فایروال‌های مسیر باز باشد و هر شبکه‌ای که ترافیک UDP را محدود یا مسدود کند، کلاینت‌ها را به HTTP/2 بازمی‌گرداند.

به‌طور مشخص، چه چیزی برای شما تغییر می‌کند؟ مرورگرها هرگز با HTTP/3 شروع نمی‌کنند. آن‌ها ابتدا از طریق HTTP/2 یا HTTP/1.1 متصل می‌شوند، هدر Alt-Svc: h3=":443"; ma=86400 را در پاسخ می‌بینند و برای اتصالات بعدی به آن میزبان از HTTP/3 استفاده می‌کنند. بنابراین این هدر یک تزئین اختیاری نیست، بلکه مکانیزم کشف پروتکل است. در nginx، پروتکل HTTP/2 در نسخه 1.25.1 به یک دستورالعمل مستقل تبدیل شد (http2 on; در داخل بلوک server که جایگزین پارامتر قدیمی listen ... http2 شد) و QUIC در نسخه mainline 1.25.0 ارائه شد، جایی که یک سایت HTTP/3 به listen 443 quic reuseport; در کنار listen 443 ssl; معمولی نیاز دارد.

پروکسی‌ها از نظر بلوغ در این زمینه متفاوت هستند و ارزش دارد که این موضوع را با نسخه‌ای که واقعاً اجرا می‌کنید بررسی کنید. تا اوت 2026، Caddy به‌صورت پیش‌فرض و بدون نیاز به پیکربندی، HTTP/3 را ارائه می‌دهد. nginx به شنونده صریح quic به همراه هدر Alt-Svc که در بالا توضیح داده شد، نیاز دارد. Traefik آن را برای هر نقطه ورودی (entry point) از طریق گزینه صریح http3 فعال می‌کند. اگر TLS را در Traefik در مقابل چندین برنامه Docker خاتمه دهید، نسخه پروتکلی که بازدیدکنندگان دریافت می‌کنند در همان‌جا تعیین می‌شود و ارتباط بین پروکسی و کانتینر شما معمولاً HTTP/1.1 ساده است، فارغ از اینکه مرورگر چه پروتکلی را مذاکره کرده باشد.

به جای فرض کردن، بررسی کنید. دستور curl --http3 -sS -o /dev/null -D - https://example.com/ تنها در صورتی کار می‌کند که curl -V شامل HTTP3 در ویژگی‌های خود باشد و اکثر نسخه‌های توزیع‌شده شامل آن نیستند. بررسی قابل‌اطمینان، لاگ خودتان است: $server_protocol را به فرمت لاگ اضافه کنید و ببینید مرورگرهای واقعی چه چیزی را مذاکره می‌کنند. پیش از هر کاری، تأیید کنید که پورت UDP 443 واقعاً باز است، زیرا فایروالی که فقط TCP 443 را مجاز می‌داند، باعث می‌شود HTTP/3 بدون خطا از کار بیفتد در حالی که سایت همچنان از طریق HTTP/2 به کار خود ادامه می‌دهد. دانستن اینکه کدام پورت‌ها در سرور لینوکس شما باز و در حال گوش دادن هستند اولین چیزی است که باید بررسی کنید.

HTTPS: HTTP پروتکل است، TLS پوشش آن

HTTPS یک پروتکل مجزا نیست. این همان درخواست‌ها و کدهای وضعیت است که درون یک نشست TLS منتقل می‌شوند. پورت 80 آن‌ها را به‌صورت متن آشکار (clear text) و پورت 443 آن‌ها را به‌صورت رمزنگاری‌شده منتقل می‌کند. ابتدا handshake مربوط به TLS تکمیل می‌شود و سپس درخواست HTTP درون کانال رمزنگاری‌شده ارسال می‌گردد. به همین دلیل است که مشکلات گواهی (certificate) هرگز کد وضعیت HTTP ندارند: خطا پیش از ارسال حتی یک بایت از داده‌های HTTP رخ می‌دهد، بنابراین پاسخی برای شماره‌گذاری وجود ندارد.

در سروری که چندین سایت را میزبانی می‌کند، یک نکته در ترتیب عملیات اهمیت دارد. گواهی با استفاده از SNI (نشان‌گر نام سرور) انتخاب می‌شود؛ فیلدی در handshake مربوط به TLS که نام میزبان را پیش از وجود هرگونه هدر HTTP به‌صورت آشکار منتقل می‌کند. بنابراین سرور ابتدا گواهی را بر اساس SNI انتخاب کرده و سپس در مرحله دوم، virtual host را از هدر Host برمی‌گزیند. این‌ها دو جستجوی مجزا هستند که معمولاً با هم مطابقت دارند. هنگامی که چنین نباشد، مرورگر خطای عدم تطابق نام مانند NET::ERR_CERT_COMMON_NAME_INVALID را نمایش می‌دهد و هیچ درخواستی ارسال نمی‌کند، زیرا گواهی سرور پیش‌فرض برای نامی ارائه شده که آن را پوشش نمی‌دهد.

برای یک سایت عمومی، یک گواهی معتبر تهیه کنید و اجازه دهید به‌طور خودکار تمدید شود. Certbot با Let's Encrypt روی nginx مسیرهای گواهی را در server block شما می‌نویسد و تایمر تمدید را برایتان نصب می‌کند. برای نام میزبان‌هایی که هیچ مرجع عمومی نمی‌تواند آن‌ها را تأیید کند، مانند نام‌های داخلی یا آدرس IP خام در شبکه شخصی، گواهی خودامضا (self-signed) روی Ubuntu گزینه صادقانه‌ای است، به شرط آنکه بپذیرید هر کلاینت باید برای اعتماد به آن تنظیم شود.

هنگامی که TLS به‌درستی کار کرد، تمام ترافیک پورت 80 را به پورت 443 هدایت کنید:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

هدر Strict-Transport-Security را تنها زمانی اضافه کنید که از عملکرد خود مطمئن هستید. هدر add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; به مرورگرها دستور می‌دهد که برای آن نام میزبان، از پذیرش HTTP ساده به مدت دو سال خودداری کنند؛ مرورگرها این دستور را در کش خود ذخیره می‌کنند و این یعنی حذف هدر در آینده، اثر آن را از بین نمی‌برد. کار را با یک max-age چند ساعته شروع کنید، مطمئن شوید که تمام زیردامنه‌ها واقعاً روی HTTPS هستند، سپس آن را افزایش دهید.

FAQ

تفاوت HTTP و HTTPS چیست؟

پروتکل HTTPS در واقع همان HTTP است که درون یک نشست TLS (امنیت لایه انتقال) ارسال می‌شود. متدها و کدهای وضعیت در هر دو یکسان هستند. تفاوت در این است که بایت‌ها بین کلاینت و هر آنچه که TLS را خاتمه می‌دهد (terminate می‌کند) رمزنگاری می‌شوند و پورت پیش‌فرض از 80 به 443 تغییر می‌کند. از آنجا که دست‌دادن (handshake) پروتکل TLS پیش از ارسال اولین بایت HTTP تکمیل می‌شود، خطای گواهی هرگز منجر به تولید کد وضعیت نمی‌شود؛ به همین دلیل است که هشدار گواهی در مرورگر، نام خطا مانند NET::ERR_CERT_COMMON_NAME_INVALID را نشان می‌دهد و نه یک عدد مانند 403.

چرا سایت من خطای 502 Bad Gateway برمی‌گرداند؟

خطای 502 از سمت nginx به این معناست که nginx نتوانسته پاسخ قابل‌استفاده‌ای از upstream که به آن پروکسی می‌کند دریافت کند؛ بنابراین درخواست بازدیدکننده مشکلی نداشته و ایراد در بخشی پشت nginx است. /var/log/nginx/error.log را مطالعه کنید. connect() failed (111: Connection refused) while connecting to upstream به این معناست که هیچ سرویسی روی آدرس و پورت مشخص‌شده در proxy_pass گوش نمی‌دهد؛ پس بررسی کنید که برنامه در حال اجرا باشد و روی آدرسی که انتظار دارید bind شده باشد. no live upstreams while connecting to upstream یعنی تمام سرورهای موجود در بلوک upstream پس از شکست‌های مکرر، از دسترس خارج (down) شده‌اند. این وضعیت را با 504 Gateway Timeout مقایسه کنید که به این معناست که upstream اتصال را پذیرفته اما در بازه زمانی proxy_read_timeout پاسخی ارسال نکرده است.

چرا لاگ دسترسی من برای همه بازدیدکنندگان یک IP یکسان را نشان می‌دهد؟

زیرا $remote_addr آدرسی را ثبت می‌کند که اتصال TCP را برقرار کرده است و پشت یک reverse proxy یا شبکه توزیع محتوا (CDN)، آن آدرس همان پروکسی است. آدرس واقعی بازدیدکننده در هدر X-Forwarded-For ارسال می‌شود. در پروکسی، proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; را تنظیم کنید و سپس در nginx مقصد، set_real_ip_from را روی محدوده آدرس‌های پروکسی تنظیم کرده و real_ip_header X-Forwarded-For; را فعال کنید. فقط محدوده‌هایی را لیست کنید که تحت کنترل شما هستند؛ زیرا این هدر متنی است که هر کلاینتی می‌تواند آن را ارسال کند و اعتماد به آن از کل اینترنت، به بازدیدکننده اجازه می‌دهد آدرسی که شما لاگ می‌کنید یا بر اساس آن rate limit اعمال می‌کنید را جعل کند.

آیا نیاز است HTTP/2 یا HTTP/3 را فعال کنم؟

فعال‌سازی HTTP/2 توصیه می‌شود، زیرا تنها با یک دستور در سایتی که TLS دارد فعال شده و محدودیت تعداد درخواست در هر اتصال را که باعث کندی صفحات دارای فایل‌های کوچک زیاد می‌شود، از بین می‌برد. HTTP/3 دستاورد کوچک‌تر و نامطمئن‌تری است و هزینه آن باز کردن پورت UDP 443 و نیاز به بیلد کردن پروکسی با پشتیبانی از QUIC است. به یاد داشته باشید که مرورگرها تنها پس از مشاهده هدر Alt-Svc در یک پاسخ قبلی به HTTP/3 سوئیچ می‌کنند؛ بنابراین بدون آن هدر، صرف‌نظر از آنچه در خط listen نوشته‌اید، تغییری رخ نمی‌دهد. $server_protocol را به فرمت لاگ خود اضافه کنید و پیش از صرف وقت، اندازه‌گیری کنید که بازدیدکنندگان واقعاً از چه پروتکلی استفاده می‌کنند.

خطای 403 Forbidden در حالی که فایل وجود دارد به چه معناست؟

در یک سایت استاتیک، خطای 403 معمولاً مربوط به مجوزهای سیستم‌فایل است تا قوانین HTTP. خطای open() ... failed (13: Permission denied) در /var/log/nginx/error.log به این معناست که کاربر worker در nginx نمی‌تواند فایل را بخواند؛ این اتفاق اغلب به این دلیل رخ می‌دهد که دایرکتوری والد مجوز execute برای دیگران (others) ندارد، نه اینکه مجوز خود فایل اشتباه باشد. directory index of ... is forbidden به این معناست که درخواست به دایرکتوری‌ای ارجاع شده که فایل index ندارد و autoindex نیز خاموش است. یک قانون صریح deny در بلوک location منطبق نیز می‌تواند منجر به بازگشت 403 شود؛ بنابراین اگر لاگ خطا چیزی نشان نمی‌دهد، آن بلوک را بررسی کنید.

#http#https#web-server#headers#http3