ما هو HTTP؟ دليل مسؤول الخادم
افهم HTTP من منظور مسؤول الخادم: الطرق ورموز الحالة والرؤوس المهمة، وما يسجله nginx، وكيف يرتبط HTTP/3 وTLS بطلباتك واستجاباتك اليومية.
ما هو HTTP؟
HTTP (بروتوكول نقل النص التشعبي) هو مجموعة القواعد التي يستخدمها العميل وخادم الويب لطلب شيء وإرساله. يرسل العميل طلباً يتضمن طريقة مثل GET، ومساراً مثل /pricing، وإصدار البروتوكول، وقائمة من الرؤوس، وأحياناً نص الطلب. يجيب الخادم برمز حالة مثل 200، ثم يرسل رؤوسه الخاصة وعادةً نص الاستجابة. كل عرض لصفحة وكل استدعاء لـAPI (واجهة برمجة التطبيقات) على خادمك هو عملية التبادل نفسها، وتتكرر باستمرار.
لا يحتفظ HTTP بحالة خاصة به. لا يتذكر الخادم ما طلبته قبل ثانية، لذلك تُنقل أي معلومات تؤدي وظيفة الذاكرة، مثل جلسة تسجيل الدخول، في رأس مع كل طلب. تفسر هذه الخاصية جانباً كبيراً مما يأتي: تعتمد التخزين المؤقت بالكامل على الرؤوس، ويمكن لموازن التحميل إرسال طلبك التالي إلى backend مختلف من دون أن يتسبب ذلك في أي مشكلة.
يوضح كل ما يلي شكل هذا النموذج من جهة الخادم، في access log وفي إعدادات nginx.
طلب واستجابة خامّان مع تعليقات
فيما يلي طلب HTTP/1.1 كامل. ينهي السطر الفارغ الرؤوس، ويكون كل ما يأتي بعد ذلك السطر هو جسم الطلب. لا يحتوي GET عادةً على جسم.
GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzipGETهي الطريقة، وتحدّد الإجراء المطلوب. تقرأGETالبيانات، وترسلPOSTالبيانات، وتستبدلPUTالمورد، وتحذفDELETEالمورد، وتطلبHEADرؤوسGETمن دون الجسم./pricingهو المسار. لا يُعد اسم المضيف جزءاً من سطر الطلب، ولذلك يوجد الرأس التالي.HTTP/1.1هو إصدار البروتوكول الذي يستخدمه العميل.Host: example.comيحدّد الموقع الذي يريده العميل. يتطلب HTTP/1.1 هذا الرأس، ولذلك يجيب nginx على الطلب الذي لا يحتوي عليه بـ400 Bad Request.- أما البقية فهي تفضيلات. يوضّح
Accept-Encoding: gzipأن العميل يستطيع فك الضغط، ولذلك يُسمح للخادم بضغط الجسم.
للاستجابة البنية نفسها، مع وجود سطر الحالة في الأعلى.
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بدلاً منه، ويحدّد النهاية بقطعة ذات طول صفري.- يوضّح
Cache-Controlللمتصفح ولأي ذاكرة تخزين مؤقت بينهما مدة اعتبار هذه الاستجابة حديثة. عندما يتجاوز عمرهاmax-ageثانية تصبح قديمة، وهذا لا يعني حذفها؛ فقد تحتفظ ذاكرة التخزين المؤقت بنسخة قديمة وتعيد التحقق منها مع خادمك بدلاً من تنزيل الجسم كاملاً مرة أخرى. - يفصل السطر الفارغ بعد الرؤوس بينها وبين الجسم في كلا الاتجاهين.
أسماء الرؤوس غير حساسة لحالة الأحرف، وينتهي كل سطر بحرف إرجاع متبوعاً بتغذية سطر، وليس بسطر جديد مجرد. لن تكتب هذه المحارف يدوياً، لكنك ستراها في التقاط الحزم.
لمراقبة زوج حقيقي، نفّذ هذا الأمر على موقع تملكه:
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 (بروتوكول التحكم في النقل). خلف Proxy، يكون هذا عنوان الـProxy وليس عنوان الزائر، ما لم تضبط وحدة realip الموضحة لاحقاً لاستبداله.-الأول هو عنصر نائب ثابت. أما الثاني فهو$remote_user، ولا يُملأ إلا عند استخدام مصادقة HTTP الأساسية."GET /pricing HTTP/1.1"هو$request، أي سطر الطلب الأصلي الكامل. مع ميزة escaping الافتراضية للسجلات في nginx، تُكتب علامة الاقتباس المزدوجة والشرطة المائلة العكسية وأي byte أقل من 32 أو أكبر من 126 بصيغة\xXX، لذلك يكون السطر مطابقاً للمحتوى، لكنه لا يطابق دائماً البايتات واحداً لواحد.200هي الحالة التي أعادها خادمك، وليست الحالة التي رآها الزائر.5310هو$body_bytes_sent، أي body وحده. لا تُحتسب response headers، لذلك يكون هذا الرقم دائماً أصغر من عدد الـbytes المُرسلة فعلياً.- الحقلان الأخيران المحاطان بعلامتي اقتباس هما
RefererوUser-Agent. كلاهما يأتي من العميل، لذلك يمكن أن يحتوي أي منهما على أي قيمة.
تُسجَّل البيانات غير الصالحة أيضاً، ولكن بصيغة escaped. يترك العميل الذي يتحدث TLS (أمان طبقة النقل) إلى المنفذ plaintext 80 سطراً من نوع 400، ويبدأ حقل الطلب فيه ببايتات escaped مثل "\x16\x03\x01\x02\x00\x01". يمثّل \x16 نوع سجل TLS handshake، لذلك تكون تلك البايتات بداية 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 يشير إلى اسم مضيف خاطئ. استخدم 302 أثناء اختبار إعادة التوجيه. 304 Not Modified نجاح وليس خطأ: أرسل العميل طلباً شرطياً، إما If-None-Match يحمل ETag (وسم كيان) ما زلت تتعرّف إليه، أو If-Modified-Since يتضمن تاريخاً لم يتغير المورد منذه، لذلك رددتَ بالرؤوس من دون محتوى. يعني امتلاء السجل بـ304s أن التخزين المؤقت يعمل.
4xx تعني أن الطلب غير صحيح. 400 Bad Request يعني أن الإدخال مشوّه. 401 Unauthorized يعني فعلياً أن العميل غير موثَّق، ويجب أن يتضمن ترويسة WWW-Authenticate التي تحدد المخطط. 403 Forbidden يعني أن الطلب فُهم، لكن رُفض رغم ذلك. 404 Not Found هو مسار غير موجود، أو مسار لا يريد الخادم الإقرار بوجوده، لأن بعض التطبيقات ترسل 404 بدلاً من 403 لإخفاء مورد عن العملاء الذين لا يملكون صلاحية الوصول. 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) يعني أن مستخدم عامل nginx لا يستطيع قراءة الملف، وغالباً لأن دليلاً أباً يفتقر إلى إذن التنفيذ للآخرين. directory index of "/srv/site/" is forbidden يعني أن المسار أُحل إلى دليل لا يحتوي على ملف فهرس، بينما يكون autoindex معطلاً.
5xx تعني أن المشكلة من جانبك. 500 هو خطأ الخادم العام، وفي موقعك عادةً ما يشير إلى خطأ غير معالَج في التطبيق. 502 Bad Gateway يعني أن nginx لم يتمكن من الحصول على استجابة صالحة من الخادم الأعلى، ويحدد سجل الأخطاء السبب: connect() failed (111: Connection refused) while connecting to upstream يعني أنه لا يوجد شيء يستمع على العنوان الموجود في proxy_pass. 504 Gateway Timeout يعني أن nginx توقف عن انتظار الخادم الأعلى. الحالة المعتادة هي أن الخادم الأعلى قبل الاتصال ثم لم يرسل أي شيء خلال proxy_read_timeout، وقيمته الافتراضية 60 ثانية، ويسجل السجل ذلك على النحو upstream timed out (110: Connection timed out) while reading response header from upstream. وتنتهي أيضاً محاولة اتصال لا تكتمل خلال proxy_connect_timeout، وقيمته الافتراضية 60 ثانية كذلك، بالرمز 504، ثم ينتهي سطر السجل بـwhile connecting to upstream. 503 Service Unavailable يعني أن الخادم غير قادر مؤقتاً على معالجة الطلب، عادةً لأنه محمّل فوق طاقته أو متوقف بسبب صيانة مجدولة. لاحظ أن محدد معدل الطلبات المضمّن في 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. إذا أعاد المضيف الافتراضي الجديد الموقع الخطأ، فالمشكلة في الغالب هي أن الاسم لم يطابق، ولذلك انتقل الطلب إلى الخادم الافتراضي. اختبر ذلك من دون تعديل DNS:
curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/User-Agent وصف يكتبه العميل عن نفسه، وهو نص حر. استخدمه كمؤشر عند قراءة السجلات. لا تستخدمه كآلية تحكم، لأن العميل الذي يريد تزويره يستطيع ذلك ببساطة. لذلك فإن حظر أداة scraping باستخدام 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 الأصول التي يحتوي اسم ملفها على تجزئة للمحتوى، لأن الاسم يتغير عند تغير المحتوى. يجب استخدام no-store لأي محتوى خاص بالمستخدم، لأن ذاكرة التخزين المؤقت المشتركة التي تحتفظ بصفحة بعد تسجيل الدخول ستسلّمها إلى الشخص التالي الذي يطلب عنوان URL نفسه. أما private فهو الإعداد المتوسط: يمكن للمتصفح الاحتفاظ بالمحتوى، لكن لا يجوز لذاكرة التخزين المؤقت المشتركة ذلك.
X-Forwarded-For موجود لأن الوكيل يخفي عنوان الزائر. بعد مرور الطلب عبر reverse proxy، يصبح $remote_addr عنوان الوكيل. لذلك سترى السجلات وتحديد الموقع الجغرافي وتقييد معدل الطلبات عميلاً واحداً فقط، إلى أن تستعيد العنوان الأصلي. يجب على الوكيل تمرير العنوان الأصلي:
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;ينتمي هذان التوجهان إلى وحدة realip في nginx. تستبدل الوحدة $remote_addr بالعنوان المُمرَّر، وتحتفظ بعنوان الاتصال نفسه في $realip_remote_addr. إذا ترجّمت nginx بنفسك، فأضف --with-http_realip_module، لأن الوحدة لا تُبنى افتراضياً.
أدرج النطاقات التي تتحكم فيها فقط. 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 بالفعل، ولذلك يتوقف التطبيق عن إعادة التوجيه.
HTTP/1.1 مقابل HTTP/2 مقابل HTTP/3: ما الذي سيتغير بالنسبة إليك
HTTP/1.1 بروتوكول نصي، وعملياً يعالج طلباً واحداً في كل مرة لكل اتصال. يتيح Connection: keep-alive للطلب التالي إعادة استخدام اتصال TCP نفسه، ما يوفر تكلفة إنشاء الاتصال، لكن الاستجابات تظل تصل بالترتيب الذي أُرسلت به الطلبات. يسمح البروتوكول أيضاً بالـpipelining، حيث يرسل العميل عدة طلبات من دون انتظار الإجابات، لكن قاعدة الترتيب تظل مطبقة، ولا يفعّل أي متصفح حديث الـpipelining افتراضياً. تحجب الاستجابة البطيئة كل ما ينتظر خلفها. يُسمى ذلك حجب رأس الصف، وتتغلب المتصفحات عليه بفتح عدة اتصالات باسم المضيف نفسه في الوقت نفسه.
يحافظ HTTP/2 على الأساليب نفسها ورموز الحالة نفسها، لكنه يغيّر طريقة التأطير إلى صيغة ثنائية. تتشارك طلبات كثيرة اتصالاً واحداً بصفتها streams مستقلة، كما يُضغط نص الرؤوس المتكرر، وهذا مهم لأن الطلب الحديث يحمل قدراً كبيراً منه. يظل الاتصال يستخدم TCP، لذلك تؤدي حزمة مفقودة واحدة إلى إيقاف كل streams على ذلك الاتصال حتى تصل إعادة الإرسال. لم يختفِ حجب رأس الصف. بل انتقل من HTTP إلى طبقة النقل. كان Server Push جزءاً من HTTP/2، لكنه اختفى عملياً لأن Chrome أزال دعمه في 2022.
يحافظ HTTP/3 على الدلالات نفسها مرة أخرى، ويستبدل TCP بـQUIC، وهو بروتوكول نقل مبني على UDP (بروتوكول مخطط بيانات المستخدم). تكون streams في QUIC مستقلة حتى الطبقات الأدنى، لذلك لا تؤدي الحزمة المفقودة إلا إلى إيقاف الـstream الذي تنتمي إليه. يندمج TLS 1.3 في عملية مصافحة QUIC بدلاً من وضعه فوقها، لذلك يحتاج الاتصال الجديد إلى عدد أقل من جولات ذهاب وإياب. وينتج عن ذلك أمران عمليان: يجب فتح منفذ UDP الذي يستخدمه خادمك لـHTTP/3 في كل جدار ناري على المسار، وأي شبكة تقيّد UDP أو تحجبه ستعيد العملاء إلى HTTP/2. يكون هذا المنفذ عادةً 443، لكن البروتوكول يسمح للخادم باستخدام أي منفذ UDP، لأن الإعلان الذي يوجّه المتصفحات إلى HTTP/3 يذكر المنفذ صراحةً دائماً.
ما الذي سيتغير بالنسبة إليك عملياً؟ لا تبدأ المتصفحات عادةً باستخدام HTTP/3. تتصل عبر HTTP/2 أو HTTP/1.1، وترى ترويسة Alt-Svc: h3=":443"; ma=86400 في الاستجابة، ثم تستخدم HTTP/3 للاتصالات اللاحقة بالمضيف نفسه. الاستثناء هو سجل HTTPS في DNS الذي تسرد قيمة alpn فيه h3، ما يتيح لمتصفح مثل Firefox فتح HTTP/3 في الاتصال الأول مباشرةً. هذا السجل تلميح إضافي وليس بديلاً، لذلك ليست الترويسة زينة اختيارية. إنها آلية الاكتشاف الرئيسية. في nginx، أصبح HTTP/2 توجيهاً مستقلاً في الإصدار 1.25.1 (http2 on; داخل كتلة server، ليحل محل معلمة listen ... http2 القديمة)، ووصل QUIC إلى الإصدار الرئيسي 1.25.0، حيث يحتاج موقع HTTP/3 إلى listen 443 quic reuseport; إلى جانب listen 443 ssl; المعتاد. لا تُبنى وحدة HTTP/3 افتراضياً (--with-http_v3_module)، وما زالت وثائق nginx تصف دعمها بأنه تجريبي.
تختلف درجة نضج الـproxies هنا، ويستحق ذلك التحقق من الإصدار الذي تشغّله فعلياً. اعتباراً من نوفمبر 2026، يقدّم Caddy خدمة HTTP/3 افتراضياً من دون إعداد. يحتاج nginx إلى مستمع quic صريح، إضافةً إلى ترويسة Alt-Svc الموضحة أعلاه. يفعّله Traefik لكل entry point عبر خيار http3 صريح. إذا أنهيت TLS عند Traefik أمام عدة تطبيقات Docker، فإن إصدار البروتوكول الذي يحصل عليه زوارك يُحدَّد هناك، بينما تكون القفزة من الـproxy إلى الحاوية عادةً عبر HTTP/1.1 عادي مهما كان البروتوكول الذي تفاوض عليه المتصفح.
تحقق بدلاً من الافتراض. يعمل curl --http3 -sS -o /dev/null -D - https://example.com/ فقط إذا كانت curl -V تسرد HTTP3 ضمن ميزاتها، ومعظم إصدارات التوزيعات لا تتضمنه. والتحقق الموثوق هو السجل الخاص بك: أضف $server_protocol إلى التنسيق، واقرأ ما تتفاوض عليه المتصفحات الفعلية. قبل كل ذلك، تأكد من أن منفذ UDP الذي يستخدمه مستمع quic، وهو 443 في الإعداد أعلاه، مفتوح فعلياً، لأن جداراً نارياً يسمح بـTCP 443 فقط سيجعل HTTP/3 يفشل بصمت، بينما يظل الموقع يعمل عبر HTTP/2. إن معرفة المنافذ المفتوحة والتي تستمع على خادم Linux هي أول ما يجب التحقق منه.
HTTPS: HTTP هو البروتوكول، وTLS هو الغلاف
HTTPS ليس بروتوكولاً منفصلاً. فهو ينقل الطلبات نفسها ورموز الحالة نفسها داخل جلسة TLS. ينقلها المنفذ 80 بنص واضح، بينما ينقلها المنفذ 443 بشكل مشفّر. تكتمل مصافحة TLS أولاً، ثم ينتقل طلب HTTP داخل القناة المشفّرة. ولهذا لا ترتبط مشكلة الشهادة أبداً برمز حالة: يحدث الفشل قبل إرسال أي بايت من HTTP، لذلك لا توجد استجابة تحمل رقماً.
توجد نقطة مهمة تتعلق بالترتيب على خادم يستضيف عدة مواقع. تُختار الشهادة باستخدام SNI (مؤشر اسم الخادم)، وهو حقل في مصافحة TLS يحمل اسم المضيف بنص واضح قبل وجود أي ترويسة HTTP. لذلك يختار الخادم الشهادة من SNI أولاً، ثم يختار المضيف الافتراضي من الترويسة Host ثانياً. هذان بحثان منفصلان، لكنهما يتوافقان عادةً. وعندما لا يتوافقان، يعرض المتصفح خطأ عدم تطابق الاسم مثل NET::ERR_CERT_COMMON_NAME_INVALID ولا يرسل أي طلب على الإطلاق، لأن شهادة الخادم الافتراضي عُرضت لاسم لا تغطيه.
للموقع العام، احصل على شهادة حقيقية ودَعها تجدّد نفسها. يكتب Certbot مع Let's Encrypt على nginx مسارات الشهادة داخل كتلة الخادم، ويثبّت مؤقت التجديد نيابةً عنك. أما اسم المضيف الذي لا تستطيع أي جهة عامة التحقق منه، مثل اسم داخلي أو عنوان IP مجرد على شبكتك الخاصة، فـالشهادة الموقعة ذاتياً على 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، وانتقال المنفذ الافتراضي من 80 إلى 443. لأن مصافحة 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. تحقق من أن التطبيق يعمل ويستمع في المكان المتوقع. يعني no live upstreams while connecting to upstream أن كل خادم في كتلة upstream وُضع في حالة عدم التشغيل بعد إخفاقات متكررة. قارنه مع 504 Gateway Timeout، الذي يعني أن nginx انتهت مهلته أثناء انتظار الخادم upstream، إما لاكتمال الاتصال خلال proxy_connect_timeout أو لوصول الاستجابة خلال proxy_read_timeout.
لماذا يعرض سجل الوصول عنوان IP نفسه لكل زائر؟
لأن $remote_addr يسجل العنوان الذي فتح اتصال TCP. خلف Reverse Proxy أو شبكة توصيل محتوى، يكون ذلك العنوان هو عنوان الـproxy. يصل عنوان الزائر في ترويسة X-Forwarded-For بدلاً من ذلك. اضبط proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; على الـproxy، ثم اضبط set_real_ip_from في nginx المستقبِل على نطاق عناوين الـproxy، واضبط real_ip_header X-Forwarded-For;. أدرج النطاقات التي تتحكم فيها فقط، لأن أي عميل يمكنه إرسال هذه الترويسة كنص. إذا وثقت بها من كامل الإنترنت، فسيتمكن الزائر من اختيار العنوان الذي تسجله والعنوان الذي تطبق عليه حد معدل الطلبات.
هل أحتاج إلى تفعيل HTTP/2 أو HTTP/3؟
يستحق HTTP/2 التفعيل، لأنه يتطلب توجيهاً واحداً على موقع يستخدم TLS بالفعل، كما أنه يزيل حد الطلبات لكل اتصال الذي يجعل الصفحات التي تحتوي على ملفات صغيرة كثيرة بطيئة. أما HTTP/3 فمكسبه أصغر وأقل يقيناً، ويتطلب فتح منفذ UDP، عادةً 443، إضافة إلى إصدار من الـproxy يدعم QUIC. تذكر أن المتصفحات تنتقل عادةً إلى HTTP/3 فقط بعد أن ترى ترويسة Alt-Svc في استجابة سابقة، أو سجل DNS لـHTTPS يسرد h3. لذلك لن يتغير شيء من دون أحدهما، مهما كان محتوى سطر listen. أضف $server_protocol إلى تنسيق السجل، وقِس البروتوكول الذي يتفاوض عليه الزوار فعلياً قبل تخصيص الوقت لذلك.
ماذا يعني 403 Forbidden عندما يكون الملف موجوداً؟
في الموقع الثابت، يكون 403 عادةً متعلقاً بصلاحيات نظام الملفات، وليس بقاعدة HTTP. يعني open() ... failed (13: Permission denied) في /var/log/nginx/error.log أن مستخدم عامل nginx لا يستطيع قراءة الملف. يحدث ذلك غالباً لأن دليلاً أباً يفتقر إلى صلاحية التنفيذ للآخرين، وليس لأن وضع الملف نفسه خاطئ. يعني directory index of ... is forbidden أن الطلب تطابق مع دليل لا يحتوي على ملف فهرس، بينما يكون autoindex معطلاً. كما تعيد قاعدة deny صريحة داخل كتلة location المطابقة الرمز 403. لذلك اقرأ تلك الكتلة عندما لا يعرض سجل الأخطاء أي شيء.