في مقال خدمات Cloudflare وبدائلها مفتوحة المصدر شرحنا ما تقدمه كل خدمة من Cloudflare، وختمناه بقائمة قصيرة بعنوان كيف تستخدم Cloudflare مع سيرفرك بشكل صحيح. وهذه القائمة صحيحة، ولكن كل بند فيها يخفي خلفه إعداداً حقيقياً على سيرفرك، وكل إعداد منها له خطأ شائع يجعل الحماية بلا فائدة دون أن تلاحظ ذلك، فالموقع يعمل والقفل يظهر في المتصفح، بينما يمر نصف الطريق مكشوفاً، أو يصل أي أحد إلى سيرفرك مباشرة ويتجاوز Cloudflare بالكامل.
لذلك سوف نأخذ هذه القائمة في هذا المقال وننفذها بنداً بنداً، ولكي لا يكون الكلام نظرياً فقد بنينا مختبراً صغيراً على جهاز واحد يقلد Cloudflare وسيرفرك وزائراً عادياً ومهاجماً يعرف عنوان سيرفرك، ثم كسرنا كل إعداد عمداً لنرى الخطأ الذي يراه الزائر أو المهاجم. ولم نغير أي شيء في حساب Cloudflare حقيقي، فكل ما يخص لوحة التحكم مأخوذ من توثيق Cloudflare الرسمي ومربوط به.
وسوف نناقش في هذا المقال ما يلي:
- المختبر: كيف نقلد Cloudflare وسيرفرك على جهازك.
- أي السجلات تجعلها Proxied وأيها DNS only.
- أوضاع التشفير SSL/TLS، ولماذا Flexible خطر، ولماذا لا يكفي Full.
- شهادة Origin CA من Cloudflare أم شهادة Let's Encrypt على سيرفرك.
- قفل السيرفر على عناوين Cloudflare بسكربت يبني قواعد nftables من القائمة الرسمية، وما الذي يتغير إذا كان ال Reverse Proxy داخل Docker.
- عنوان الزائر الحقيقي من الترويسة
CF-Connecting-IPدون أن يزوره أحد. - Authenticated Origin Pulls: شهادة تثبت أن الطلب جاء من منطقتك أنت في Cloudflare.
- قواعد التخزين المؤقت Cache Rules التي تسرب صفحات المستخدمين، وقائمة تحقق في النهاية.
المختبر: نسخة مصغرة من Cloudflare على جهازك
لا يمكنك أن تشغل Cloudflare على جهازك بالطبع، ولكن ما يهمنا هنا هو ما يحدث بين Cloudflare وسيرفرك، وهذا الجزء هو اتصال HTTP أو HTTPS عادي يأتي من عنوان IP معين، لذلك يمكن تقليده بدقة كافية. والفكرة أن يكون لدينا أربع Containers مبنية كلها على Docker Image واحدة هي nginx:1.29-alpine بعد إضافة nftables وcurl وopenssl إليها:
- lab-cf-edge: Nginx يقوم بدور حافة Cloudflare Edge، فيستقبل الزائر عبر HTTPS ثم يتصل بسيرفرك بحسب وضع التشفير الذي نختاره، ويضيف الترويسة
CF-Connecting-IPكما تفعل Cloudflare. وقد وضعناه في شبكة عنوانها173.245.48.0/24، وهي جزء من النطاق173.245.48.0/20الموجود في قائمة عناوين Cloudflare الرسمية، لذلك يرى سيرفرك الطلب قادماً من عنوان Cloudflare فعلاً. - lab-cf-origin: سيرفرك الأصلي Origin Server، وهو Nginx يرد بعنوان من يتصل به.
- lab-cf-visitor: زائر عادي يفتح
https://www.example.comفيصل إلى الحافة. - lab-cf-attacker: مهاجم عرف عنوان سيرفرك الحقيقي، فيتصل به مباشرة دون المرور بالحافة.
والزائر والمهاجم في شبكة ثانية عنوانها 198.51.100.0/24، وهو نطاق مخصص للتوثيق والأمثلة بحسب RFC 5737، والشبكتان من نوع --internal فلا تخرج منهما أي حركة إلى الإنترنت ولا تتعارض مع عناوين Cloudflare الحقيقية خارج المختبر. والأوامر التالية تبني المختبر:
cat > Dockerfile <<'EOF'
FROM nginx:1.29-alpine
RUN apk add --no-cache nftables curl openssl
EOF
docker build -q -t lab-cf-tools:1 .
docker network create --internal --subnet 173.245.48.0/24 lab-cf-edgenet
docker network create --internal --subnet 198.51.100.0/24 lab-cf-internet
# the origin also stays on the default bridge, so it can download the Cloudflare lists
docker run -d --name lab-cf-origin --cap-add NET_ADMIN \
-v "$PWD/origin/default.conf:/etc/nginx/conf.d/default.conf:ro" \
-v "$PWD/origin/cf-firewall.sh:/usr/local/bin/cf-firewall.sh:ro" \
-v "$PWD/certs:/etc/nginx/certs:ro" lab-cf-tools:1 \
sh -c 'echo "# placeholder" > /etc/nginx/cloudflare-realip.conf; exec /docker-entrypoint.sh nginx -g "daemon off;"'
docker network connect --ip 173.245.48.80 --alias origin lab-cf-edgenet lab-cf-origin
docker network connect --ip 198.51.100.80 lab-cf-internet lab-cf-origin
docker run -d --name lab-cf-edge --network lab-cf-edgenet --ip 173.245.48.10 \
-v "$PWD/edge/modes:/modes:ro" -v "$PWD/certs:/etc/nginx/certs:ro" lab-cf-tools:1
docker network connect --ip 198.51.100.2 lab-cf-internet lab-cf-edge
docker run -d --name lab-cf-visitor --network lab-cf-internet --ip 198.51.100.10 \
--add-host www.example.com:198.51.100.2 -v "$PWD/certs/edge.pem:/edge.pem:ro" lab-cf-tools:1 sleep infinity
docker run -d --name lab-cf-attacker --network lab-cf-internet --ip 198.51.100.66 \
--add-host www.example.com:198.51.100.80 -v "$PWD/certs/origin-ca.pem:/origin-ca.pem:ro" lab-cf-tools:1 sleep infinityفي الأوامر أعلاه لاحظ التالي:
- الخيار
--add-hostيجعل الاسمwww.example.comعند الزائر يشير إلى الحافة، تماماً كما يفعل سجل Proxied، وعند المهاجم يشير إلى عنوان سيرفرك الحقيقي، وهذا ما يحصل عليه المهاجم من سجل DNS قديم أو من سجل البريد. - الصلاحية
NET_ADMINتسمح ل nftables بإضافة قواعد جدار الحماية داخل ال Container الخاص بالسيرفر فقط، دون أن نلمس جدار الحماية في جهازك. - الحافة ستقرأ إعدادها من المجلد
/modes، وفيه ملف لكل وضع تشفير، ونبدل بينها بنسخ الملف ثمnginx -s reload.
أما الشهادات فقد أنشأناها كلها بالأمر openssl لهذا المختبر فقط: جهة Lab Origin CA تقوم بدور Cloudflare Origin CA وتوقع شهادة سيرفرك، وجهة Lab AOP CA للشهادة التي تقدمها الحافة لسيرفرك، وشهادة موقعة ذاتياً لمنتحل يتظاهر بأنه سيرفرك. وإعداد السيرفر الأصلي في البداية هو التالي:
log_format cf '$remote_addr from=$realip_remote_addr "$request" $status';
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/certs/origin.pem;
ssl_certificate_key /etc/nginx/certs/origin.key;
include /etc/nginx/cloudflare-realip.conf;
access_log /var/log/nginx/cf.log cf;
location / {
return 200 "origin says: your address is $remote_addr\n";
}
location /account {
add_header Cache-Control "private, no-store" always;
return 200 "account page of one signed-in user\n";
}
}والسجل cf يكتب عنوانين في كل سطر: $remote_addr وهو العنوان الذي يعتمده Nginx بعد معالجة الترويسات، و$realip_remote_addr وهو عنوان من فتح الاتصال فعلاً، وسوف نحتاج إلى الفرق بينهما لاحقاً.
أي السجلات تجعلها Proxied وأيها DNS only؟
شرحنا معنى السحابة البرتقالية في شرح DNS وسجلاته، والقاعدة العملية بحسب توثيق Proxy status أن السجلات من نوع A وAAAA وCNAME وحدها يمكن أن تكون Proxied، وأن السجلات الأخرى مثل MX وTXT تبقى DNS only دائماً. وفيما يلي ما ننصح به لكل نوع من الخدمات:
| السجل | الحالة | والسبب |
|---|---|---|
www والنطاق الرئيسي Root Domain والمواقع والتطبيقات وال API | Proxied | حركة ويب على منافذ يدعمها ال Proxy، فتمر عبر الحماية والتخزين المؤقت ويختفي عنوان سيرفرك |
mail وسجل MX | DNS only | البريد يستخدم SMTP وIMAP وليس HTTP، وال Proxy لا يحمله أصلاً |
| SSH وWireGuard وقواعد البيانات | DNS only، أو لا يوجد سجل أصلاً | بروتوكولات ليست ويب، والأفضل ألا يكون لها اسم عام، وأن تصل إليها عبر شبكة WireGuard خاصة |
| سجلات التحقق من الملكية لخدمات خارجية | DNS only | الجهة الخارجية تبحث عن القيمة التي أعطتك إياها، وليس عن عنوان Cloudflare |
| تطبيق ويب على منفذ غير معتاد مثل 3000 | انقله خلف ال Reverse Proxy على 443 | ال Proxy يحمل منافذ محددة فقط، وهي في HTTP: 80 و8080 و8880 و2052 و2082 و2086 و2095، وفي HTTPS: 443 و2053 و2083 و2087 و2096 و8443 بحسب توثيق المنافذ |
وهناك ثلاث ملاحظات من التوثيق نفسه يقع فيها الكثيرون: الأولى أن الاسم الذي له أكثر من سجل A وأحدها Proxied يعامل كله كأنه Proxied، والثانية أن مدة الحفظ TTL للسجلات Proxied ثابتة على Auto وهي 300 ثانية ولا يمكن تغييرها، والثالثة وهي الأهم أن تحويل السجل إلى Proxied لا يخفي عنوان سيرفرك القديم، لأن سجلات DNS القديمة محفوظة في مواقع تجمع تاريخ DNS. لذلك ينصح دليل Cloudflare لحماية السيرفر الأصلي بأن تغير عنوان IP سيرفرك بعد إضافة النطاق إلى Cloudflare، وألا تضع خادم البريد على السيرفر نفسه إن أمكن، لأن رسالة الرد على عنوان غير موجود تكشف عنوان خادم البريد للمهاجم، وأن تراجع سجلات TXT وSPF حتى لا تحمل عنوان سيرفرك.
أوضاع التشفير SSL/TLS: من Off إلى Full (strict)
عندما يمر موقعك عبر Cloudflare يصبح هناك اتصالان وليس اتصالاً واحداً: الأول بين الزائر وCloudflare، والثاني بين Cloudflare وسيرفرك، ووضع التشفير SSL/TLS Encryption Mode يحدد ما يحدث في الاتصال الثاني فقط، لأن الاتصال الأول يكون عبر HTTPS بشهادة Cloudflare في كل الأوضاع ما عدا Off. والصورة التالية تبين الأوضاع الأربعة التي تراها في لوحة التحكم:

وبحسب توثيق أوضاع التشفير فإن معنى كل وضع هو التالي:
- Off: لا يوجد تشفير في أي من الاتصالين، وكل شيء HTTP.
- Flexible: الزائر يتصل عبر HTTPS، ثم تتصل Cloudflare بسيرفرك عبر HTTP دون تشفير، فلا تحتاج إلى شهادة على سيرفرك.
- Full: تتصل Cloudflare بسيرفرك بالبروتوكول الذي استخدمه الزائر، فإذا كان HTTPS اتصلت عبر HTTPS ولكن دون أن تتحقق من شهادة سيرفرك.
- Full (strict): مثل Full، ولكن الشهادة على سيرفرك يجب أن تكون غير منتهية، وصادرة من جهة موثوقة عامة أو من Cloudflare Origin CA، وتحمل اسم النطاق المطلوب في CN أو SAN، كما يشرح توثيق Full (strict).
وهناك وضع خامس اسمه Strict (SSL-Only Origin Pull) يجعل الاتصال بسيرفرك عبر HTTPS حتى لو جاء الزائر عبر HTTP، وهو متاح لخطة Enterprise فقط. ولاحظ أن النطاقات الجديدة تبدأ اليوم بوضع Automatic SSL/TLS، حيث تفحص Cloudflare سيرفرك وترفع الوضع إلى الأعلى أماناً إذا وجدته يدعمه، ولا تخفضه أبداً، ولكن هذا الفحص يحدث تقريباً مرة في الشهر بحسب التوثيق، لذلك لا تعتمد عليه، واختر Full (strict) بنفسك من Custom SSL/TLS بعد أن تجهز الشهادة كما في القسم التالي.
لماذا Flexible خطر؟ حلقة التحويل في المختبر
وقد يتساءل البعض: إذا كان Flexible يعمل دون شهادة على السيرفر، وكان الزائر يرى القفل، فما المشكلة؟ والإجابة أن هناك مشكلتين. الأولى أمنية، فالاتصال بين Cloudflare وسيرفرك يمر عبر الإنترنت دون تشفير، وفيه كلمات المرور وملفات تعريف الارتباط Cookies التي أرسلها الزائر وهو يظن أن اتصاله مشفر، لذلك يقول التوثيق صراحة إن التطبيقات التي فيها تسجيل دخول أو بيانات شخصية يجب أن تستخدم Full أو Full (strict). والثانية عملية، وهي أشهر سبب لخطأ ERR_TOO_MANY_REDIRECTS، والمختبر يبينها بوضوح. فالسيرفر الأصلي في إعدادنا يحول كل طلب HTTP إلى HTTPS، وهذا إعداد صحيح ومنتشر، والحافة في وضع Flexible تتصل به عبر HTTP:
# edge/modes/flexible.conf: lab stand-in for the Cloudflare edge
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/certs/edge.pem;
ssl_certificate_key /etc/nginx/certs/edge.key;
location / {
proxy_pass http://origin;
proxy_set_header Host $host;
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
}الآن نفعل هذا الوضع ونفتح الموقع من الزائر مع متابعة التحويلات:
docker exec lab-cf-edge sh -c 'cp /modes/flexible.conf /etc/nginx/conf.d/default.conf && nginx -s reload'
docker exec lab-cf-visitor curl -sS --cacert /edge.pem -L --max-redirs 5 https://www.example.com/
docker exec lab-cf-visitor curl -sS --cacert /edge.pem -I https://www.example.com/ | grep -i 'HTTP/\|location'
docker logs lab-cf-origin | grep 'GET /' | tail -2والمخرج سوف يكون كما يلي:
curl: (47) Maximum (5) redirects followed
HTTP/1.1 301 Moved Permanently
Location: https://www.example.com/
173.245.48.10 - - [08/Oct/2026:23:18:37 +0000] "GET / HTTP/1.1" 301 169 "-" "curl/8.17.0" "-"
173.245.48.10 - - [08/Oct/2026:23:18:37 +0000] "GET / HTTP/1.1" 301 169 "-" "curl/8.17.0" "-"في المخرج أعلاه لاحظ التالي:
- الزائر طلب
https://www.example.com/فحصل على تحويل إلى العنوان نفسه تماماً، لأن السيرفر رأى الطلب قد وصله عبر HTTP فحوله إلى HTTPS، والزائر يتبع التحويل فيعود إلى الحافة التي تتصل بالسيرفر عبر HTTP مرة أخرى، وهكذا حتى يتوقف curl بعد خمس محاولات، ويتوقف المتصفح بالخطأ ERR_TOO_MANY_REDIRECTS. - سجل السيرفر يبين أن الطلبات وصلت إلى المنفذ 80 من
173.245.48.10، أي من الحافة، ودون أي تشفير.
والحل البسيط الذي يفعله الكثيرون هو حذف التحويل من السيرفر ليعمل الموقع، وبهذا يختفي الخطأ ويبقى نصف الطريق مكشوفاً، وهذا الحل غير مناسب إطلاقاً. أما الحل الصحيح فهو شهادة على السيرفر ووضع Full (strict)، كما ينص توثيق Flexible نفسه.
Full يقبل أي شهادة: سيرفر منتحل في المختبر
بعد ذلك ينتقل الكثيرون إلى Full ويتوقفون عنده، لأن الموقع يعمل بشهادة موقعة ذاتياً Self-signed دون أي جهد. والفرق بين Full وFull (strict) في المختبر هو سطر proxy_ssl_verify، فملف وضع Full فيه:
proxy_pass https://origin;
proxy_ssl_verify off;أما ملف وضع Full (strict) ففيه:
proxy_pass https://origin;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /etc/nginx/certs/origin-ca.pem;
proxy_ssl_server_name on;
proxy_ssl_name www.example.com;ومع السيرفر الحقيقي يعمل الوضعان بالنتيجة نفسها، فكلاهما يعيد origin says: your address is 173.245.48.10. والسؤال الحقيقي هو: ماذا لو لم يكن الطرف الآخر سيرفرك؟ لنفرض أن مهاجماً استطاع أن يحول الاتصال بين Cloudflare وسيرفرك إلى جهازه، عبر سجل DNS عدله في حساب مخترق أو عبر مسار شبكة، فهو لن يملك شهادتك، ولكنه يستطيع أن يقدم شهادة موقعة ذاتياً بالاسم نفسه. وفي المختبر نزيل السيرفر الأصلي من شبكة الحافة ونضع مكانه Container منتحلاً بالاسم نفسه origin:
docker network disconnect lab-cf-edgenet lab-cf-origin
docker run -d --name lab-cf-impostor --network lab-cf-edgenet --ip 173.245.48.99 --network-alias origin \
-v "$PWD/impostor.conf:/etc/nginx/conf.d/default.conf:ro" -v "$PWD/certs:/etc/nginx/certs:ro" lab-cf-tools:1
# Full
docker exec lab-cf-edge sh -c 'cp /modes/full.conf /etc/nginx/conf.d/default.conf && nginx -s reload'
docker exec lab-cf-visitor curl -sS --cacert /edge.pem https://www.example.com/
# Full (strict)
docker exec lab-cf-edge sh -c 'cp /modes/strict.conf /etc/nginx/conf.d/default.conf && nginx -s reload'
docker exec lab-cf-visitor curl -sS --cacert /edge.pem -o /dev/null -w '%{http_code}\n' https://www.example.com/
docker logs lab-cf-edge 2>&1 | grep 'upstream SSL' | tail -1والمخرج سوف يكون كما يلي:
impostor says: send me your passwords
502
2026/10/08 23:18:52 [error] 106#106: *16 upstream SSL certificate verify error: (18:self-signed certificate) while SSL handshaking to upstream, client: 198.51.100.10, server: www.example.com, request: "GET / HTTP/1.1", upstream: "https://173.245.48.99:443/", host: "www.example.com"في المخرج أعلاه لاحظ التالي:
- في وضع Full وصل الزائر إلى المنتحل دون أي تحذير، والقفل في متصفحه مغلق، لأن الحافة شفرت الاتصال فعلاً ولكن مع الطرف الخطأ. وهذا ما يقصده توثيق Full حين يقول إن شهادة السيرفر لا يتم التحقق منها بأي شكل، وإن طرفاً خبيثاً يستطيع أن يقدم شهادته.
- في وضع Full (strict) رفضت الحافة الاتصال لأن الشهادة موقعة ذاتياً، وظهر للزائر الخطأ 502. وCloudflare في هذه الحالة تعرض الخطأ 526 Invalid SSL certificate، وهو الخطأ نفسه الذي سوف تراه إذا انتهت شهادة سيرفرك أو لم تحمل اسم النطاق.
بعد التجربة نحذف المنتحل ونعيد السيرفر الأصلي إلى شبكة الحافة بالأمر docker network connect --ip 173.245.48.80 --alias origin lab-cf-edgenet lab-cf-origin، ونبقي الحافة على وضع Full (strict) في بقية المقال.
شهادة Origin CA أم Let's Encrypt على سيرفرك؟
وضع Full (strict) يحتاج إلى شهادة صالحة على سيرفرك، ولها طريقتان، وشرحنا الشهادات وصيغها وسلسلة الثقة في الشهادات الرقمية TLS وX.509:
- شهادة Let's Encrypt عادية يصدرها ال Reverse Proxy ويجددها تلقائياً، كما في Caddy وNginx Proxy Manager. وهنا يفضل التحقق من ملكية النطاق عبر ال DNS (DNS Challenge) بمفتاح API من Cloudflare، لأن التحقق عبر HTTP (HTTP Challenge) يمر عبر Cloudflare ويتأثر بقواعدها. وميزة هذه الشهادة أن المتصفح يثق بها، فإذا جعلت السجل DNS only يوماً ما فلن يتعطل شيء.
- شهادة Cloudflare Origin CA المجانية في كل الخطط، وتنشئها من صفحة SSL/TLS ثم Origin Server ثم Create Certificate، وتختار مدتها، وتغطي النطاق الرئيسي و
*.example.comبشكل افتراضي، ثم تنسخ الشهادة والمفتاح الخاص إلى ملفين على سيرفرك، ولاحظ أن المفتاح الخاص لا يظهر مرة أخرى بعد أن تغلق الصفحة.
وشهادة Origin CA موقعة من جهة لا يثق بها أي متصفح، والأمر التالي يقرأ الشهادة الرئيسية Root Certificate التي ينشرها التوثيق نفسه:
curl -s -o origin_ca_rsa_root.pem https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem
openssl x509 -in origin_ca_rsa_root.pem -noout -subject -enddateوالمخرج سوف يكون كما يلي:
subject=C=US, O=CloudFlare, Inc., OU=CloudFlare Origin SSL Certificate Authority, L=San Francisco, ST=California
notAfter=Aug 15 17:00:00 2029 GMTوهذا يعني أن شهادة Origin CA تعمل فقط والسجل Proxied، فإذا جعلته DNS only أو أوقفت Cloudflare مؤقتاً فسوف يرى الزوار خطأ في الشهادة، كما يحذر التوثيق. وهناك نقطة أخرى ذكرها التوثيق تحت عنوان Known limitation، وهي أن Cloudflare لا ترسل تنبيهاً قبل انتهاء شهادات Origin CA، لذلك سجل تاريخ انتهاء شهادتك في نظام المراقبة عندك. أما إعدادها في Nginx فهو السطران المعتادان، وهما في المختبر:
ssl_certificate /etc/nginx/certs/origin.pem;
ssl_certificate_key /etc/nginx/certs/origin.key;والخلاصة في هذا الاختيار أن Let's Encrypt أنسب إذا كان ال Reverse Proxy يجدد الشهادات بنفسه أصلاً، وأن Origin CA أنسب إذا كان السيرفر لا يستطيع أن يصدر شهادة، أو كنت تريد شهادة واحدة طويلة المدة لعدة سيرفرات خلف Cloudflare.
اقفل السيرفر على عناوين Cloudflare
كل ما سبق يحمي الطريق الذي يمر عبر Cloudflare، ولكن المهاجم الذي يعرف عنوان سيرفرك لا يستخدم هذا الطريق أصلاً، وإنما يتصل بسيرفرك مباشرة فيتجاوز WAF وصد DDoS وقواعد التخزين المؤقت. والصورة التالية تبين الطبقات الثلاث التي سوف نبنيها في الأقسام القادمة، ومن يتوقف عند كل طبقة:

لماذا لا تكفي قواعد تكتبها مرة واحدة؟
الحل البسيط هو أن تنسخ النطاقات من صفحة عناوين Cloudflare إلى جدار الحماية مرة واحدة، أو أن تضيفها بحلقة تكرار كما فعلنا مع ufw في المقال السابق. وهذا يعمل في اليوم الأول، ولكن له ثلاث مشاكل تظهر مع الوقت: القائمة تتغير أحياناً، ويقول توثيق عناوين Cloudflare إن النطاقات الجديدة تضاف إلى القائمة قبل أن تستخدم، فإذا لم تحدث قواعدك فسوف تحظر جزءاً من زوارك يوماً ما دون أن تعرف السبب. وإذا شغلت الحلقة مرة ثانية لتحدث القائمة تكررت القواعد ولم تحذف النطاقات القديمة. والأخطر أن تحميل القائمة قد يفشل أو يعيد صفحة خطأ، فيبني السكربت قائمة فارغة ويغلق الموقع على الجميع.
والحل الأنسب هو سكربت يحمل القائمتين الرسميتين ips-v4 وips-v6، ويتحقق من محتواهما، ثم يبني جدولاً كاملاً في nftables فيه مجموعتان Sets للعناوين، ويستبدل الجدول القديم بخطوة واحدة، ويبني في الوقت نفسه ملفات Nginx التي تعتمد على القائمة نفسها. وهذا هو السكربت كاملاً:
#!/bin/sh
# cf-firewall.sh: let only Cloudflare reach ports 80 and 443, and trust CF-Connecting-IP only from Cloudflare.
# Builds both files from the official lists, checks them, then applies them in one step.
set -eu
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
curl -fsS --max-time 20 https://www.cloudflare.com/ips-v4 -o "$TMP/v4"
curl -fsS --max-time 20 https://www.cloudflare.com/ips-v6 -o "$TMP/v6"
# A failed or strange download must never become an empty allow list
echo >> "$TMP/v4"; echo >> "$TMP/v6"
sed -i '/^$/d' "$TMP/v4" "$TMP/v6"
if grep -Evq '^[0-9.]+/[0-9]+$' "$TMP/v4" || grep -Evq '^[0-9a-f:]+/[0-9]+$' "$TMP/v6"; then
echo "unexpected content in the Cloudflare lists, nothing changed" >&2; exit 1
fi
N4=$(wc -l < "$TMP/v4"); N6=$(wc -l < "$TMP/v6")
if [ "$N4" -lt 10 ] || [ "$N6" -lt 5 ]; then
echo "lists too short ($N4 IPv4, $N6 IPv6), nothing changed" >&2; exit 1
fi
V4=$(paste -sd, "$TMP/v4"); V6=$(paste -sd, "$TMP/v6")
cat > "$TMP/cloudflare.nft" <<NFT
table inet cloudflare_origin
delete table inet cloudflare_origin
table inet cloudflare_origin {
set cf_v4 { type ipv4_addr; flags interval; elements = { $V4 } }
set cf_v6 { type ipv6_addr; flags interval; elements = { $V6 } }
chain input {
type filter hook input priority filter; policy accept;
tcp dport { 80, 443 } ip saddr @cf_v4 accept
tcp dport { 80, 443 } ip6 saddr @cf_v6 accept
tcp dport { 80, 443 } counter drop
}
}
NFT
{ echo "# generated by cf-firewall.sh on $(date -u +%F)"
sed 's/.*/set_real_ip_from &;/' "$TMP/v4" "$TMP/v6"
echo "real_ip_header CF-Connecting-IP;"; } > "$TMP/cloudflare-realip.conf"
{ echo 'geo $realip_remote_addr $from_cloudflare {'
echo ' default 0;'
sed 's/.*/ & 1;/' "$TMP/v4" "$TMP/v6"
echo '}'; } > "$TMP/cloudflare-geo.conf"
nft -c -f "$TMP/cloudflare.nft" # syntax check only
cp "$TMP/cloudflare-realip.conf" "$TMP/cloudflare-geo.conf" /etc/nginx/
nginx -t -q
nft -f "$TMP/cloudflare.nft" # replaces the old table atomically
nginx -s reload
echo "applied: $N4 IPv4 and $N6 IPv6 Cloudflare ranges"في السكربت أعلاه لاحظ التالي:
- الخيار
-fفي curl يجعل أي رد خطأ من السيرفر فشلاً، وset -eيوقف السكربت عند أول أمر يفشل، فلا يصل إلى مرحلة التطبيق أبداً بقائمة ناقصة. - كل سطر في القائمتين يجب أن يكون نطاقاً بصيغة CIDR، وعدد النطاقات يجب ألا يقل عن 10 في IPv4 و5 في IPv6، وهذا حد أدنى اخترناه نحن للحماية من قائمة مقطوعة، وليس رقماً من Cloudflare.
- الأسطر الثلاثة الأولى في ملف nftables هي الطريقة المعروفة لاستبدال جدول كامل: إنشاء الجدول إن لم يكن موجوداً، ثم حذفه، ثم بناؤه من جديد، وكل ذلك في ملف واحد يطبقه
nft -fكعملية واحدة Atomic، فلا توجد لحظة يكون فيها السيرفر دون قواعد، ولا تتكرر القواعد مهما شغلت السكربت. - سياسة السلسلة
policy accept، والإسقاط Drop يخص المنفذين 80 و443 فقط، لذلك لا يقطع السكربت اتصال SSH ولا أي خدمة أخرى، ويعمل بجانب جدار الحماية الذي لديك. وإذا كان موقعك يستخدم منفذاً آخر من منافذ Cloudflare مثل 8443 فأضفه إلى القائمة. - الأمر
nft -cيفحص الصيغة دون تطبيق، وnginx -tيفحص ملفات Nginx الجديدة قبل إعادة التحميل. - ملف
cloudflare-realip.confسوف نشرحه في قسم عنوان الزائر، وملفcloudflare-geo.confنحتاجه عندما يكون Nginx داخل Docker.
الآن نشغل السكربت في السيرفر الأصلي ونعرض السلسلة التي بناها:
docker exec lab-cf-origin cf-firewall.sh
docker exec lab-cf-origin nft list chain inet cloudflare_origin inputوالمخرج سوف يكون كما يلي:
2026/10/08 23:19:02 [notice] 73#73: signal process started
applied: 15 IPv4 and 7 IPv6 Cloudflare ranges
table inet cloudflare_origin {
chain input {
type filter hook input priority filter; policy accept;
tcp dport { 80, 443 } ip saddr @cf_v4 accept
tcp dport { 80, 443 } ip6 saddr @cf_v6 accept
tcp dport { 80, 443 } counter packets 0 bytes 0 drop
}
}السطر الأول رسالة من nginx -s reload، والسطر الثاني يبين أن القائمة في أكتوبر 2026 فيها 15 نطاقاً من IPv4 و7 من IPv6. والآن نختبر الطريقين، الزائر عبر الحافة، والمهاجم مباشرة على المنفذين 443 و80:
docker exec lab-cf-visitor curl -sS --cacert /edge.pem https://www.example.com/
docker exec lab-cf-attacker curl -sS -k -m 5 -H 'CF-Connecting-IP: 8.8.8.8' https://www.example.com/
docker exec lab-cf-attacker curl -sS -m 5 http://www.example.com/
docker exec lab-cf-origin nft list chain inet cloudflare_origin input | grep counterوالمخرج سوف يكون كما يلي:
origin says: your address is 198.51.100.10
curl: (28) Connection timed out after 5002 milliseconds
curl: (28) Connection timed out after 5002 milliseconds
tcp dport { 80, 443 } counter packets 6 bytes 360 dropفي المخرج أعلاه لاحظ التالي:
- الزائر وصل لأن الطلب جاء إلى السيرفر من الحافة
173.245.48.10، وهذا العنوان داخل النطاق173.245.48.0/20في المجموعةcf_v4. - المهاجم لم يحصل على أي رد، لأن القاعدة تسقط الحزم Drop ولا ترفضها، فينتظر حتى تنتهي المهلة دون أن يعرف إن كان السيرفر موجوداً أصلاً.
- العداد
counterسجل 6 حزم أسقطها، وهو طريقة سريعة لتعرف أن هناك من يحاول الوصول إلى سيرفرك مباشرة.
والآن نكسر السكربت عمداً لنتأكد أنه لا يغلق الموقع عند الفشل، فنفصل السيرفر عن الإنترنت ونشغله مرة أخرى:
docker network disconnect bridge lab-cf-origin
docker exec lab-cf-origin cf-firewall.sh; echo "exit=$?"
docker exec lab-cf-origin sh -c 'nft list set inet cloudflare_origin cf_v4 | grep -o "[0-9.]*/[0-9]*" | wc -l'والمخرج سوف يكون كما يلي:
curl: (6) Could not resolve host: www.cloudflare.com (DNS server returned general failure)
exit=6
15فشل التحميل فتوقف السكربت برمز خطأ، وبقيت النطاقات الخمسة عشر التي طبقت في المرة السابقة كما هي. وعلى السيرفر الحقيقي ضع السكربت في /usr/local/sbin/cf-firewall.sh، وشغله مرة عند الإقلاع لأن جداول nftables التي تضيفها بالأمر nft لا تبقى بعد إعادة التشغيل، ومرة كل أسبوع لتحديث القائمة، وذلك بملف في /etc/cron.d/cloudflare-ips:
@reboot root sleep 30 && /usr/local/sbin/cf-firewall.sh
0 4 * * 1 root /usr/local/sbin/cf-firewall.shوإذا كان سيرفرك يستخدم ufw فلا يوجد تعارض، لأن ufw على Ubuntu يضيف قواعده في جداول أخرى، والحزمة يجب أن تقبلها كل الجداول لتصل، فيكفي أن يمنعها جدولنا. وأما أوامر ufw التي تؤدي الغرض نفسه بنفس فكرة الحلقة فهي في المقال السابق.
إذا كان ال Reverse Proxy داخل Docker
هنا تأتي النقطة التي تفسد هذا الإعداد عند أغلب من يستخدم Docker. فالسلسلة input تمر بها الحزم الموجهة إلى السيرفر نفسه، أما الحزم الموجهة إلى منفذ نشرته في Docker بالشكل 443:443 فيحولها Docker إلى ال Container وتمر بسلسلة forward، فلا ترى قاعدتنا شيئاً منها، كما شرحنا في شبكات Docker وأفضل الممارسات. ولهذه الحالة ثلاثة حلول بالترتيب من الأبسط:
- Cloudflare Tunnel: لا ينشر السيرفر أي منفذ للخارج، فلا يوجد ما تقفله أصلاً، وقد شرحناه في قسم Tunnel.
- الحماية داخل Nginx نفسه، وهي ما نجربه الآن.
- قواعد في السلسلة DOCKER-USER مع مطابقة المنفذ الأصلي قبل التحويل، كما يشرح توثيق Docker مع iptables، أو جدول nftables منفصل بسلسلة لها نقطة الربط نفسها كما في توثيق Docker مع nftables. وهذا الحل لم نجربه في المختبر لأنه يغير جدار الحماية في جهاز Docker نفسه.
ولكي نقلد حالة Docker نحذف جدول nftables من السيرفر، فيصبح كأن القاعدة غير موجودة، ثم يحاول المهاجم مرة أخرى:
docker exec lab-cf-origin nft delete table inet cloudflare_origin
docker exec lab-cf-attacker curl -sS --cacert /origin-ca.pem -m 5 https://www.example.com/والمخرج سوف يكون كما يلي:
origin says: your address is 198.51.100.66وصل المهاجم مباشرة. والحل في Nginx أن نضيف إلى الإعداد ملف cloudflare-geo.conf الذي بناه السكربت، وهو يعطي المتغير $from_cloudflare القيمة 1 إذا جاء الاتصال من نطاق Cloudflare، ثم نغلق الاتصال دون رد بالرمز الخاص 444 لكل ما عدا ذلك:
include /etc/nginx/cloudflare-geo.conf;
log_format cf '$remote_addr from=$realip_remote_addr "$request" $status';
...
server {
listen 443 ssl;
...
include /etc/nginx/cloudflare-realip.conf;
access_log /var/log/nginx/cf.log cf;
if ($from_cloudflare = 0) {
return 444;
}
...
}وبعد nginx -s reload نعيد المحاولة من المهاجم ومن الزائر:
curl: (56) OpenSSL SSL_read: OpenSSL/3.5.9: error:0A000126:SSL routines::unexpected eof while reading, errno 0
origin says: your address is 198.51.100.10أغلق Nginx الاتصال في وجه المهاجم دون أي رد، ووصل الزائر كما كان. وقد يتساءل البعض: لماذا يعتمد الملف على $realip_remote_addr وليس على $remote_addr، أو لماذا لا نستخدم allow وdeny المعروفتين؟ والإجابة أن وحدة real_ip التي سوف نشرحها في القسم التالي تستبدل $remote_addr بعنوان الزائر قبل أن تصل القراءة إلى allow وdeny، فلو استخدمتهما لفحص Nginx عنوان الزائر وليس عنوان Cloudflare، ولرفض كل زوارك، أما $realip_remote_addr فهو عنوان من فتح الاتصال فعلاً. ولاحظ أن هذه الحماية أضعف من جدار الحماية، لأن المهاجم يكمل مصافحة TLS قبل أن يغلق Nginx الاتصال، ولكنها تمنعه من الوصول إلى تطبيقك، وهذا هو الأهم. وفي المختبر استخدمنا --cacert مع المهاجم بدلاً من -k لأن curl 8.17 يضيف مع -k نتيجة فحص الشهادة إلى رسالة الخطأ فتصبح مربكة، والنتيجة واحدة في الحالتين.
عنوان الزائر الحقيقي من CF-Connecting-IP
عندما يمر الموقع عبر Cloudflare يأتي كل طلب إلى سيرفرك من عنوان Cloudflare، وتضيف Cloudflare عنوان الزائر في الترويسة CF-Connecting-IP، وقد شرحنا لماذا يهمك عنوان الزائر وكيف تقرؤه في كل Reverse Proxy وإطار عمل في تطبيقك خلف Reverse Proxy. وما يهمنا هنا هو الخطأ الذي يجعل هذه الترويسة خطراً. فالحل البسيط أن تقول ل Nginx أن يصدق الترويسة من أي عنوان، لأن كتابة القائمة مملة:
set_real_ip_from 0.0.0.0/0;
real_ip_header CF-Connecting-IP;ونجرب ذلك قبل تشغيل السكربت، فيرسل المهاجم الطلب مباشرة إلى السيرفر ويكتب في الترويسة العنوان الذي يريد:
docker exec lab-cf-attacker curl -sS -k -H 'CF-Connecting-IP: 8.8.8.8' https://www.example.com/
docker exec lab-cf-origin tail -1 /var/log/nginx/cf.logوالمخرج سوف يكون كما يلي:
origin says: your address is 8.8.8.8
8.8.8.8 from=198.51.100.66 "GET / HTTP/1.1" 200صدق السيرفر أن الطلب جاء من 8.8.8.8، فإذا كان لديك حد للطلبات Rate Limit أو حظر تلقائي في fail2ban أو CrowdSec يعتمد على هذا العنوان، فالمهاجم يستطيع أن يتجاوزه بتغيير الترويسة في كل طلب، أو أن يجعلك تحظر عنواناً بريئاً. والحل أن يصدق Nginx الترويسة من نطاقات Cloudflare فقط، وهذا ما يكتبه السكربت في cloudflare-realip.conf، وأوله وآخره:
# generated by cf-firewall.sh on 2026-10-08
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
...
real_ip_header CF-Connecting-IP;وبعد تشغيل السكربت يظهر في سجل السيرفر سطران، الأول من محاولة المهاجم السابقة، والثاني من الزائر عبر الحافة:
8.8.8.8 from=198.51.100.66 "GET / HTTP/1.1" 200
198.51.100.10 from=173.245.48.10 "GET / HTTP/1.1" 200في السطر الثاني جاء الاتصال من الحافة 173.245.48.10، وهي في قائمة الثقة، فأخذ Nginx عنوان الزائر 198.51.100.10 من الترويسة، وهذا هو المطلوب. وأي طلب يأتي من خارج القائمة تبقى عنوانه كما هو مهما كتب في الترويسة، ومع جدار الحماية لن يصل هذا الطلب أصلاً. وتذكر أن السكربت يحدث هذا الملف مع قواعد nftables في كل مرة، فلا تتباعد القائمتان أبداً.
Authenticated Origin Pulls: شهادة تثبت أن الطلب من منطقتك
قائمة العناوين تثبت أن الطلب جاء من شبكة Cloudflare، ولكنها لا تثبت أنه جاء من حسابك، فعناوين Cloudflare مشتركة بين كل عملائها. وهذا يعني أن مهاجماً يستطيع أن يضيف نطاقاً في حسابه المجاني على Cloudflare، ويجعل سجله يشير إلى عنوان سيرفرك، فتصل طلباته إليك من عناوين Cloudflare، وتمر من جدار الحماية دون أن تمر بقواعد WAF الخاصة بك. وهنا يأتي Authenticated Origin Pulls واختصاراً AOP، وهو متاح في كل الخطط، حيث تقدم Cloudflare شهادة عميل Client Certificate عند كل اتصال بسيرفرك، فيما يسمى mTLS، ويرفض سيرفرك أي اتصال لا يحمل شهادة موقعة من الجهة التي يثق بها.
ولهذه الخدمة ثلاثة مستويات بحسب التوثيق:
- Global: تستخدم شهادة تقدمها Cloudflare نفسها، وهي مشتركة بين كل حسابات Cloudflare، فتثبت فقط أن الطلب جاء من شبكة Cloudflare، أي أنها لا تحل مشكلة المهاجم الذي له حساب في Cloudflare.
- Zone-level: ترفع فيها شهادتك أنت، فتثبت أن الطلب جاء من منطقتك Zone في حسابك، وهي التي ننصح بها.
- Per-hostname: شهادة مختلفة لكل اسم فرعي Subdomain.
والأمر التالي يقرأ شهادة المستوى Global التي ينشرها التوثيق، لترى أنها شهادة واحدة لكل العملاء:
curl -s -o authenticated_origin_pull_ca.pem https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
openssl x509 -in authenticated_origin_pull_ca.pem -noout -subject -enddateوالمخرج سوف يكون كما يلي:
subject=C=US, O=CloudFlare, Inc., OU=Origin Pull, L=San Francisco, ST=California, CN=origin-pull.cloudflare.net
notAfter=Nov 1 17:00:00 2029 GMTأما المستوى Zone-level فخطواته في توثيق Zone-level هي: تنشئ جهة توقيع CA خاصة بك، وتوقع بها شهادة عميل Leaf Certificate فيها basicConstraints=CA:FALSE، ثم ترفع شهادة العميل ومفتاحها إلى Cloudflare من صفحة SSL/TLS ثم Origin Server ثم Authenticated Origin Pulls، وتضع شهادة الجهة CA على سيرفرك. والأوامر التي استخدمناها في المختبر لإنشاء الجهة وشهادة العميل هي:
openssl req -x509 -newkey rsa:2048 -nodes -keyout aop-ca.key -out aop-ca.pem -days 30 -subj "/CN=Lab AOP CA"
openssl req -newkey rsa:2048 -nodes -keyout aop-client.key -out aop-client.csr -subj "/CN=www.example.com"
printf "basicConstraints=CA:FALSE\nextendedKeyUsage=clientAuth\n" > aop.ext
openssl x509 -req -in aop-client.csr -CA aop-ca.pem -CAkey aop-ca.key -CAcreateserial -out aop-client.pem -days 30 -extfile aop.extوجعلنا مدة الشهادات 30 يوماً لأنها للمختبر فقط، أما على السيرفر الحقيقي فاختر مدة أطول، وفعل تنبيه انتهاء الشهادة الذي يرسل قبل 30 يوماً و14 يوماً كما يذكر التوثيق. وعلى السيرفر نضيف سطرين إلى كتلة server الخاصة ب 443:
ssl_client_certificate /etc/nginx/certs/aop-ca.pem;
ssl_verify_client on;ويوصي التوثيق بأن تبدأ بالقيمة optional ثم تفعل AOP في Cloudflare، ثم تتأكد من السجلات، وبعدها فقط تغيرها إلى on، والسبب أنك لو بدأت ب on قبل أن تفعل AOP في لوحة التحكم لرفض سيرفرك كل الزوار. والآن نجرب الحالتين في المختبر: حافة بوضع Full (strict) دون شهادة عميل، وهي تمثل منطقة حساب آخر، ثم حافة تقدم شهادة العميل، وملفها فيه هذان السطران زيادة على وضع Full (strict):
proxy_ssl_certificate /etc/nginx/certs/aop-client.pem;
proxy_ssl_certificate_key /etc/nginx/certs/aop-client.key;والمخرج للحالتين سوف يكون كما يلي:
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
400
origin says: your address is 198.51.100.10رفض السيرفر الطلب الأول بالخطأ 400 لأنه لا يحمل شهادة عميل، وقبل الثاني. ولاحظ ثلاثة شروط يذكرها التوثيق: AOP لا يعمل في وضعي Off وFlexible، ولا يعمل مع Cloudflare Tunnel لأن السيرفر لا يستقبل اتصالات أصلاً، وشهادة Zone-level تطبق على كل السجلات Proxied في المنطقة، فإذا كان لديك سيرفر لا يطلب الشهادة فلن يتأثر، أما السيرفر الذي يطلبها فسوف يرفض كل من لا يملكها.
قواعد التخزين المؤقت دون تسريب صفحات المستخدمين
بشكل افتراضي لا تحفظ Cloudflare صفحات HTML ولا ردود JSON، وإنما تحفظ الملفات الثابتة بحسب امتدادها، كما يشرح توثيق السلوك الافتراضي. والمشكلة تبدأ عندما تكتب قاعدة Cache Rule لتسرع الموقع كله، فتختار Eligible for cache لكل المسارات. والسؤال المهم هنا: ماذا يحدث لصفحة ترد بملف تعريف ارتباط Set-Cookie، مثل صفحة حساب المستخدم؟ والإجابة في توثيق Set-Cookie:
- مع Eligible for cache وحده، تبقي Cloudflare الترويسة Set-Cookie ولا تحفظ الصفحة، فتظهر
MISSفي كل مرة، وهذا آمن. - مع Eligible for cache ومعها مدة Edge TTL من نوع Ignore cache-control header and use this TTL، أو Status code TTL، تحذف Cloudflare الترويسة Set-Cookie وتحفظ الصفحة، فتقدمها لكل من يطلب العنوان نفسه.
والحالة الثانية هي التي تسرب لوحة حساب مستخدم إلى مستخدم آخر، لأن الخيار يقول صراحة إنه يتجاهل ترويسة Cache-Control التي يرسلها تطبيقك. لذلك اتبع القواعد التالية:
- اجعل تطبيقك يرسل
Cache-Control: private, no-storeلكل صفحة خاصة بمستخدم، فالخطط Free وPro وBusiness تحترم هذه الترويسة دائماً بحسب توثيق Cache-Control ما لم تتجاهلها قاعدتك. - لا تستخدم Ignore cache-control header إلا في قاعدة تخص مسارات ثابتة فقط، مثل
/assets/و/images/. - اكتب قاعدة Bypass cache قبل غيرها لمسارات مثل
/accountو/adminو/apiولكل طلب يحمل Cookie الجلسة.
وفي المختبر يرسل المسار /account الترويسة الصحيحة، والأمر التالي يتأكد منها:
docker exec lab-cf-visitor curl -sS --cacert /edge.pem -D - -o /dev/null https://www.example.com/account | grep -i 'HTTP/\|cache-control'والمخرج سوف يكون كما يلي:
HTTP/1.1 200 OK
Cache-Control: private, no-storeوعلى موقعك الحقيقي افتح الصفحة الخاصة بعد أي قاعدة جديدة، وتأكد أن الترويسة cf-cache-status فيها BYPASS أو DYNAMIC وليست HIT، كما ذكرنا في المقال السابق.
قائمة التحقق قبل أن تعتبر الإعداد منتهياً
| البند | كيف تتحقق منه | النتيجة الصحيحة |
|---|---|---|
| سجلات الويب Proxied والبريد DNS only | dig +short www.example.com وdig +short mail.example.com | الأول يعيد عناوين Cloudflare، والثاني عنوان خادم البريد |
| وضع التشفير | صفحة SSL/TLS في لوحة التحكم | Full (strict) دون أي استثناء لصفحة أو اسم فرعي |
| شهادة السيرفر | openssl s_client -connect IP:443 -servername www.example.com من جهازك | شهادة غير منتهية تحمل اسم النطاق، مع تاريخ انتهاء مسجل في المراقبة |
| السيرفر مقفل على Cloudflare | curl -m 5 -k https://IP/ من شبكة خارج Cloudflare | انتهاء المهلة، أو إغلاق الاتصال دون رد |
| تحديث القائمة | cat /etc/cron.d/cloudflare-ips | تشغيل عند الإقلاع وكل أسبوع |
| عنوان الزائر | سجل ال Reverse Proxy بعد زيارة من هاتفك | عنوان هاتفك، وليس عنوان Cloudflare |
| AOP | طلب عبر Cloudflare، وطلب مباشر من نطاق Cloudflare دون شهادة | الأول يعمل، والثاني 400 |
| الصفحات الخاصة | curl -sI لصفحة الحساب مع Cookie | cf-cache-status ليست HIT |
وبعد الانتهاء من المختبر نحذف كل ما أنشأناه بالاسم:
docker rm -f lab-cf-origin lab-cf-edge lab-cf-visitor lab-cf-attacker
docker network rm lab-cf-edgenet lab-cf-internet
docker image rm lab-cf-tools:1أدلة تكمل هذا المقال
هذا المقال جزء من مرحلة Cloudflare وبدائله في خريطة التعلم، وفيها مقالات قادمة عن خادم DNS لنطاقاتك، والتخزين المؤقت أمام تطبيقك، وجدار حماية تطبيقات الويب WAF على سيرفرك، وبديل Cloudflare Tunnel. وهذه الأدلة التي يعتمد عليها ما شرحناه هنا:
دليلخدمات Cloudflare بكلمات بسيطة وبدائلها مفتوحة المصدرما الذي تقدمه كل خدمة من Cloudflare، وما المتاح منها مجاناً، وما البديل مفتوح المصدر الذي تشغله على سيرفرك.
دليلكيف يرى تطبيقك عنوان الزائر الحقيقي خلف ال Reverse Proxyإذا كان تطبيقك يرى عنوان ال Proxy بدلاً من الزائر أو يولد روابط http، فهذا هو السبب والإعداد الصحيح لإطار عملك.
دليلالشهادات الرقمية TLS للمبتدئين وصيغ ملفاتهاكيف يعرف المتصفح أنه يتحدث مع الموقع الحقيقي، وماذا يوجد داخل الشهادة، ولماذا تأتي ملفاتها بأكثر من صيغة.
دليلشبكات Docker وأفضل الممارسات مع Docker Composeلماذا يبقى المنفذ الذي تنشره في Compose مفتوحاً للإنترنت رغم ufw، وكيف تصل التطبيقات إلى بعضها عبر شبكات Docker دون نشر منافذ.
دليلحماية تطبيقات الويب بطبقات أمان في ال Reverse Proxyكلمة المرور وحدها لا تحمي تطبيقاً مكشوفاً على الإنترنت، لذلك نضع أمامه طبقات حماية في ال Reverse Proxy نفسه.الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- اجعل سجلات الويب وحدها Proxied، واترك البريد وكل ما ليس ويب DNS only، وغير عنوان سيرفرك بعد الانتقال إلى Cloudflare لأن عنوانه القديم محفوظ في تاريخ DNS.
- Flexible يترك نصف الطريق دون تشفير ويسبب حلقة تحويل مع أي سيرفر يحول إلى HTTPS، وFull يشفر الاتصال مع أي طرف حتى لو كان منتحلاً، لذلك استخدم Full (strict) مع شهادة Let's Encrypt أو Origin CA.
- اقفل المنفذين 80 و443 على عناوين Cloudflare بسكربت يبني القائمة من المصدر الرسمي ويتحقق منها ويطبقها بخطوة واحدة، وشغله عند الإقلاع وكل أسبوع.
- إذا كان ال Reverse Proxy داخل Docker فقواعد السلسلة input لا تحميه، فاستخدم Tunnel أو الحماية داخل Nginx بالمتغير
$realip_remote_addrأو السلسلة DOCKER-USER. - صدق الترويسة
CF-Connecting-IPمن نطاقات Cloudflare فقط، وإلا كتب كل مهاجم العنوان الذي يريد. - Authenticated Origin Pulls بمستوى Zone-level يثبت أن الطلب جاء من منطقتك أنت، لأن عناوين Cloudflare ومستوى Global مشتركة بين كل العملاء.
- لا تجمع بين Eligible for cache وتجاهل Cache-Control في قاعدة تشمل صفحات المستخدمين، لأن Cloudflare تحذف Set-Cookie وتحفظ الصفحة للجميع.
سجل التحديثات
- أكتوبر 2026: كتابة المقال، وبناء المختبر على Docker Engine 29.8.0 بال Docker Image
nginx:1.29-alpine(Nginx 1.29.8) مع nftables 1.1.5 وcurl 8.17.0 وOpenSSL 3.5.9، واختبار السكربت على قائمة عناوين Cloudflare المنشورة في 8 أكتوبر 2026 (15 نطاقاً من IPv4 و7 من IPv6)، دون أي تغيير في حساب Cloudflare حقيقي.