يمثّل Nginx المكشوف للإنترنت خط الدفاع الأول عن كل شيء يقع خلفه. بصرف النظر عن مدى متانة كود التطبيق، فإن خادم الويب المُهيَّأ بشكل خاطئ يُسرِّب سلاسل الإصدار للماسحين الضوئيين، ويقبل فيضانات طلبات غير محدودة، ويُمرِّر الترويسات التي يتحكم فيها المهاجم مباشرةً إلى الخلفية. تتناول هذه الدرس الأعمدة الأربعة التي تعدّها فرق الإنتاج في الشركات الكبرى أموراً غير قابلة للتفاوض: تحديد معدل الطلبات، وتصليب ترويسات الاستجابة، وإخفاء الإصدار والبصمات الرقمية، وتصفية أساسية بأسلوب WAF.
تحديد معدل الطلبات باستخدام limit_req
ينفِّذ Nginx خوارزمية الدلو المثقوب (Leaky Bucket) عبر الوحدة ngx_http_limit_req_module المُدمجة افتراضياً. تُعرِّف منطقة ذاكرة مشتركة تتبّع معدلات الطلبات لكل مفتاح — وعادةً ما يكون المفتاح $binary_remote_addr (عنوان IP للعميل بصيغة ثنائية، 4 بايت لـIPv4 و16 بايت لـIPv6، مما يوفر ذاكرة مقارنةً بالشكل النصي).
الأمران اللذان يعملان معاً هما:
limit_req_zone — يُعلَن في كتلة http، ويحدد المفتاح واسم المنطقة وحجم الذاكرة المشتركة والمعدل المستهدف.
limit_req — يُطبَّق داخل كتلة server أو location، ويُفعِّل المنطقة ويسمح اختيارياً بطابور انتظار burst ومعالجة nodelay.
## /etc/nginx/nginx.conf (كتلة http)
http {
# منطقة 10 ميغابايت تستوعب ~160,000 حالة IP (كل منها ~64 بايت)
# المعدل: 10 طلبات في الثانية لكل IP
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# منطقة أكثر صرامة لنقاط نهاية تسجيل الدخول والمصادقة
limit_req_zone $binary_remote_addr zone=login_limit:5m rate=1r/s;
# إرجاع 429 Too Many Requests بدلاً من 503 الافتراضي
limit_req_status 429;
# تسجيل الرفض عند مستوى التحذير فقط (يتجنب إغراق error.log)
limit_req_log_level warn;
}
## /etc/nginx/sites-enabled/app.conf (كتلة server)
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://app_backend;
}
location /login {
# burst=5: وضع ما يصل إلى 5 طلبات إضافية في قائمة الانتظار
# nodelay: خدمة طلبات الاندفاع فوراً مع احتسابها ضمن المعدل
limit_req zone=login_limit burst=5 nodelay;
proxy_pass http://app_backend;
}
}
الفرق بين burst وnodelay: بدون nodelay، تُحتجز طلبات الاندفاع في قائمة انتظار وتتسرب بالمعدل المحدد — هذا يضيف زمن استجابة لكنه لا يظهر للعميل. مع nodelay، تُخدَّم طلبات الاندفاع فوراً لكنها تستهلك فتحات الاندفاع؛ وحين تمتلئ، تحصل الطلبات الزائدة على 429. استخدم nodelay للواجهات البرمجية التي تهمّك فيها زمن الاستجابة؛ وأغفله لنقاط النهاية الحساسة كالمصادقة حيث تريد التأخير فعلاً.
في عمليات النشر عالية الحركة التي تعمل خلف موازن تحميل، سيكون $binary_remote_addr هو IP الموازن — إذ يبدو كل عميل متشابهاً. استخدم $http_x_forwarded_for أو $http_x_real_ip بدلاً من ذلك، لكن فقط بعد التحقق من صحة عناوين IP للوكلاء الموثوقين باستخدام set_real_ip_from وreal_ip_header — وإلا يمكن لأي عميل تزوير الترويسة والتحايل على تحديد المعدل كلياً.
## ثق فقط بشبكة الفرعية الخاصة بموازن التحميل
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 10.0.0.0/8;
## ثم اربط المنطقة بعنوان IP الحقيقي للعميل
limit_req_zone $remote_addr zone=real_api_limit:10m rate=10r/s;
كيف تقبل مناطق تحديد معدل Nginx الطلبات أو تضعها في قائمة انتظار أو ترفضها لكل IP باستخدام خوارزمية الدلو المثقوب.
تصليب ترويسات الاستجابة
تتعامل المتصفحات الحديثة مع ترويسات الاستجابة بوصفها توجيهات سياسة أمنية. إرسال الترويسات الصحيحة هو أحد أكثر تحسينات الأمان قدرةً وأقلّها تكلفةً — تغيير إعداد واحد يُغلق فئات كاملة من الهجمات.
## أضف إلى كتلة http أو server — تنطبق على جميع الاستجابات
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
## سياسة أمان المحتوى — اضبطها لتناسب تطبيقك؛ ابدأ بتقييد صارم
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'none';" always;
## HSTS: أخبر المتصفحات باستخدام HTTPS فقط لمدة سنة + النطاقات الفرعية
## تحذير: لا تضف هذا إلا بعد أن يعمل TLS بالكامل. يصعب التراجع عنه.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
العلامة always مهمة. بدونها، يُضيف Nginx الترويسات فقط لاستجابات 2xx. صفحات الخطأ (4xx و5xx) — التي تُقدِّمها المتصفحات أيضاً — تفتقر إلى الترويسات. مع always، تحظى كل استجابة بالحماية. استخدمها دون استثناء.
إخفاء إصدار Nginx والبصمات الرقمية
بصورة افتراضية، يُعلن Nginx عن إصداره بدقة في ترويسة الاستجابة Server وفي جميع صفحات الخطأ: nginx/1.24.0. يستخدم المهاجمون هذا للبحث عن ثغرات CVE المعروفة وصياغة استغلالات مستهدفة قبل أن يكون لديك وقت للتصحيح. كبح معلومات الإصدار ليس أمان بالغموض — إنه يرفع تكلفة المسح الآلي بصورة حقيقية.
## في كتلة http
server_tokens off; # يُزيل الإصدار من ترويسة Server وصفحات الخطأ
# تصبح ترويسة Server: "nginx" (بدون إصدار)
## لإزالة ترويسة Server بالكامل، استخدم وحدة headers_more:
## (حزمة nginx-extras في Debian/Ubuntu، أو تجميع --add-module)
# more_set_headers "Server: "; # تفريغ الترويسة بالكامل
## أخفِ إصدار PHP أيضاً إذا كنت تُوكّل إلى PHP-FPM
fastcgi_hide_header X-Powered-By;
## وأخفِ بصمات تطبيق الخلفية
proxy_hide_header X-Powered-By;
proxy_hide_header X-AspNet-Version;
proxy_hide_header X-Generator;
بصمات صفحة الخطأ: حتى مع server_tokens off، يحتوي HTML الافتراضي لصفحة خطأ Nginx على كلمة "nginx". استخدم صفحات خطأ مخصصة لإزالة هذه البصمة الأخيرة في البيئات عالية الأمان.
تصفية أساسية بأسلوب WAF في Nginx
جدار حماية تطبيق ويب كامل مثل ModSecurity (كوحدة Nginx) أو AWS WAF يجلس أمام Nginx. لكن قبل اللجوء إلى هذه الأدوات، يمكن لأوامر map وif وgeo المضمّنة في Nginx حجب نسبة كبيرة من حركة الهجمات الآلية دون أي تبعيات خارجية.
حجب أنماط الهجمات الشائعة بالمسار:
## حجب مسارات الفحص والاستغلال المعروفة
location ~* (\.php~|wp-login|xmlrpc|\.git|\.env|\.htaccess|/etc/passwd) {
deny all;
return 444; # قطع الاتصال بلا استجابة (خاص بـNginx)
}
## حجب الطلبات ذات سلاسل الاستعلام المشبوهة (SQLi / XSS)
## استخدم map في كتلة http، ثم تحقق في كتلة server
map $query_string $bad_query {
default 0;
~*(union.*select|select.*from|drop.*table) 1;
~*(script>|<script|javascript:) 1;
~*(base64_encode|eval\(|exec\() 1;
}
server {
if ($bad_query) { return 400; }
## حجب وكلاء المستخدم السيئين (روبوتات، ماسحو ثغرات)
if ($http_user_agent ~* "(nikto|sqlmap|nmap|masscan|zgrab|python-requests/2\.2)") {
return 403;
}
## حجب User-Agent الفارغ (لا يُرسل أي متصفح شرعي بدونه)
if ($http_user_agent = "") {
return 444;
}
}
تجنّب if داخل كتل location. تحمل الأمر if في Nginx دلالات خفية وخطيرة داخل سياقات الموقع (مشكلة "if is evil"). ضع if على مستوى كتلة server، أو استخدم أنماط map + return التي تُقيَّم في مرحلة إعادة الكتابة الأكثر أماناً. لقواعد WAF المعقدة، استخدم ModSecurity أو WAF حافة مخصصة — لا تبني متاهة regex في إعداد Nginx.
الحجب الجغرافي باستخدام وحدة geo: إذا كان تطبيقك يخدم شرعاً مناطق محددة فقط، يمكن للوحدة المدمجة ngx_http_geo_module حجب نطاقات IP بالكامل دون أداة خارجية. للحجب على مستوى الدولة، تتكامل MaxMind GeoIP2 عبر ngx_http_geoip2_module (محزّم بشكل منفصل).
الجمع بين كل شيء: نمط كتلة Server المُصلَّبة
تُطبِّق فرق الإنتاج هذه الضوابط على مستوى كتلة http في ملف include مشترك (/etc/nginx/conf.d/security.conf) حتى يرثها كل مضيف افتراضي دون تكرار. تذهب التجاوزات الخاصة بالموقع إلى كتلة server الفردية.
اختبر ترويسات الأمان الخاصة بك باستخدام curl -I https://yourdomain.com محلياً، واستخدم securityheaders.com وMozilla Observatory في خط CI لوضع حد أدنى لدرجة الأمان. تُشغِّل Google وشركات SaaS الكبرى هذه الفحوصات كجزء من كل دورة إصدار.
نستخدم ملفات تعريف الارتباط لتشغيل هذا الموقع وتحليل الزيارات وعرض إعلانات مخصّصة. يمكنك قبول كل ملفات تعريف الارتباط أو رفض غير الأساسية منها.
سياسة الخصوصية