> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Cloudflare أمام سيرفرك: الإعداد الصحيح خطوة بخطوة
- URL: https://arabroot.io/articles/إعداد-cloudflare-مع-سيرفرك-بشكل-صحيح/
- Published: 2026-10-09T12:00:00.000Z
- Updated: 2026-10-09T13:01:20.000Z
- Description: كيف تضع Cloudflare أمام سيرفرك بوضع Full (strict)، وتغلق سيرفرك على عناوين Cloudflare فقط، وتستعيد عنوان الزائر الحقيقي.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الشبكات والبنية التحتية, الشبكات والوصول, الاستضافة الذاتية, Cloudflare

في مقال [خدمات Cloudflare وبدائلها مفتوحة المصدر](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/) شرحنا ما تقدمه كل خدمة من Cloudflare، وختمناه بقائمة قصيرة بعنوان [كيف تستخدم Cloudflare مع سيرفرك بشكل صحيح](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/#use-correctly). وهذه القائمة صحيحة، ولكن كل بند فيها يخفي خلفه إعداداً حقيقياً على سيرفرك، وكل إعداد منها له خطأ شائع يجعل الحماية بلا فائدة دون أن تلاحظ ذلك، فالموقع يعمل والقفل يظهر في المتصفح، بينما يمر نصف الطريق مكشوفاً، أو يصل أي أحد إلى سيرفرك مباشرة ويتجاوز 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 الرسمية](https://www.cloudflare.com/ips/?ref=arabroot.io)، لذلك يرى سيرفرك الطلب قادماً من عنوان Cloudflare فعلاً.
- **lab-cf-origin**: سيرفرك الأصلي Origin Server، وهو Nginx يرد بعنوان من يتصل به.
- **lab-cf-visitor**: زائر عادي يفتح `https://www.example.com` فيصل إلى الحافة.
- **lab-cf-attacker**: مهاجم عرف عنوان سيرفرك الحقيقي، فيتصل به مباشرة دون المرور بالحافة.

والزائر والمهاجم في شبكة ثانية عنوانها `198.51.100.0/24`، وهو نطاق مخصص للتوثيق والأمثلة بحسب [RFC 5737](https://datatracker.ietf.org/doc/html/rfc5737?ref=arabroot.io)، والشبكتان من نوع `--internal` فلا تخرج منهما أي حركة إلى الإنترنت ولا تتعارض مع عناوين Cloudflare الحقيقية خارج المختبر. والأوامر التالية تبني المختبر:

```bash
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` للشهادة التي تقدمها الحافة لسيرفرك، وشهادة موقعة ذاتياً لمنتحل يتظاهر بأنه سيرفرك. وإعداد السيرفر الأصلي في البداية هو التالي:

```nginx
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 وسجلاته](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#cloudflare-proxy)، والقاعدة العملية بحسب [توثيق Proxy status](https://developers.cloudflare.com/dns/proxy-status/?ref=arabroot.io) أن السجلات من نوع 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 خاصة](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-%D9%85%D8%B9-wireguard-%D9%88-wg-easy/)                              |
| سجلات التحقق من الملكية لخدمات خارجية                       | DNS only                           | الجهة الخارجية تبحث عن القيمة التي أعطتك إياها، وليس عن عنوان Cloudflare                                                                                                                                                                       |
| تطبيق ويب على منفذ غير معتاد مثل 3000                       | انقله خلف ال Reverse Proxy على 443 | ال Proxy يحمل منافذ محددة فقط، وهي في HTTP: 80 و8080 و8880 و2052 و2082 و2086 و2095، وفي HTTPS: 443 و2053 و2083 و2087 و2096 و8443 بحسب [توثيق المنافذ](https://developers.cloudflare.com/fundamentals/reference/network-ports/?ref=arabroot.io) |

وهناك ثلاث ملاحظات من التوثيق نفسه يقع فيها الكثيرون: الأولى أن الاسم الذي له أكثر من سجل A وأحدها Proxied يعامل كله كأنه Proxied، والثانية أن مدة الحفظ TTL للسجلات Proxied ثابتة على Auto وهي 300 ثانية ولا يمكن تغييرها، والثالثة وهي الأهم أن تحويل السجل إلى Proxied لا يخفي عنوان سيرفرك القديم، لأن سجلات DNS القديمة محفوظة في مواقع تجمع تاريخ DNS. لذلك ينصح [دليل Cloudflare لحماية السيرفر الأصلي](https://developers.cloudflare.com/fundamentals/security/protect-your-origin-server/?ref=arabroot.io) بأن تغير عنوان IP سيرفرك بعد إضافة النطاق إلى Cloudflare، وألا تضع خادم البريد على السيرفر نفسه إن أمكن، لأن رسالة الرد على عنوان غير موجود تكشف عنوان خادم البريد للمهاجم، وأن تراجع سجلات TXT وSPF حتى لا تحمل عنوان سيرفرك.

## أوضاع التشفير SSL/TLS: من Off إلى Full (strict)

عندما يمر موقعك عبر Cloudflare يصبح هناك اتصالان وليس اتصالاً واحداً: الأول بين الزائر وCloudflare، والثاني بين Cloudflare وسيرفرك، ووضع التشفير SSL/TLS Encryption Mode يحدد ما يحدث في الاتصال الثاني فقط، لأن الاتصال الأول يكون عبر HTTPS بشهادة Cloudflare في كل الأوضاع ما عدا Off. والصورة التالية تبين الأوضاع الأربعة التي تراها في لوحة التحكم:

![رسم يوضح أوضاع التشفير Off وFlexible وFull وFull strict، ولكل وضع اتصال الزائر مع Cloudflare واتصال Cloudflare مع السيرفر، مع قفل مفتوح حيث يمر الاتصال دون تشفير وقفل مغلق حيث يكون مشفراً، وعلامة تحقق حيث تتأكد Cloudflare من شهادة السيرفر](https://arabroot.io/content/images/2026/10/cloudflare-setup-01-ssl-modes.webp)

القفل الذي يراه الزائر يخص الاتصال الأول فقط، والوضع الذي تختاره يحدد الاتصال الثاني

وبحسب [توثيق أوضاع التشفير](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/?ref=arabroot.io) فإن معنى كل وضع هو التالي:

- **Off**: لا يوجد تشفير في أي من الاتصالين، وكل شيء HTTP.
- **Flexible**: الزائر يتصل عبر HTTPS، ثم تتصل Cloudflare بسيرفرك عبر HTTP دون تشفير، فلا تحتاج إلى شهادة على سيرفرك.
- **Full**: تتصل Cloudflare بسيرفرك بالبروتوكول الذي استخدمه الزائر، فإذا كان HTTPS اتصلت عبر HTTPS ولكن دون أن تتحقق من شهادة سيرفرك.
- **Full (strict)**: مثل Full، ولكن الشهادة على سيرفرك يجب أن تكون غير منتهية، وصادرة من جهة موثوقة عامة أو من Cloudflare Origin CA، وتحمل اسم النطاق المطلوب في CN أو SAN، كما يشرح [توثيق Full (strict)](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full-strict/?ref=arabroot.io).

وهناك وضع خامس اسمه 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:

```nginx
# 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;
    }
}
```

الآن نفعل هذا الوضع ونفتح الموقع من الزائر مع متابعة التحويلات:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/flexible/?ref=arabroot.io) نفسه.

### Full يقبل أي شهادة: سيرفر منتحل في المختبر

بعد ذلك ينتقل الكثيرون إلى Full ويتوقفون عنده، لأن الموقع يعمل بشهادة موقعة ذاتياً Self-signed دون أي جهد. والفرق بين Full وFull (strict) في المختبر هو سطر `proxy_ssl_verify`، فملف وضع Full فيه:

```nginx
        proxy_pass https://origin;
        proxy_ssl_verify off;
```

أما ملف وضع Full (strict) ففيه:

```nginx
        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`:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full/?ref=arabroot.io) حين يقول إن شهادة السيرفر لا يتم التحقق منها بأي شكل، وإن طرفاً خبيثاً يستطيع أن يقدم شهادته.
- في وضع Full (strict) رفضت الحافة الاتصال لأن الشهادة موقعة ذاتياً، وظهر للزائر الخطأ 502\. وCloudflare في هذه الحالة تعرض [الخطأ 526 Invalid SSL certificate](https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-526/?ref=arabroot.io)، وهو الخطأ نفسه الذي سوف تراه إذا انتهت شهادة سيرفرك أو لم تحمل اسم النطاق.

بعد التجربة نحذف المنتحل ونعيد السيرفر الأصلي إلى شبكة الحافة بالأمر `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](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/):

- **شهادة Let's Encrypt عادية** يصدرها ال Reverse Proxy ويجددها تلقائياً، كما في [Caddy](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-caddy-reverse-proxy/) و[Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/). وهنا يفضل التحقق من ملكية النطاق عبر ال DNS (DNS Challenge) بمفتاح API من Cloudflare، لأن التحقق عبر HTTP (HTTP Challenge) يمر عبر Cloudflare ويتأثر بقواعدها. وميزة هذه الشهادة أن المتصفح يثق بها، فإذا جعلت السجل DNS only يوماً ما فلن يتعطل شيء.
- **شهادة** [**Cloudflare Origin CA**](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/?ref=arabroot.io) المجانية في كل الخطط، وتنشئها من صفحة SSL/TLS ثم Origin Server ثم Create Certificate، وتختار مدتها، وتغطي النطاق الرئيسي و`*.example.com` بشكل افتراضي، ثم تنسخ الشهادة والمفتاح الخاص إلى ملفين على سيرفرك، ولاحظ أن المفتاح الخاص لا يظهر مرة أخرى بعد أن تغلق الصفحة.

وشهادة Origin CA موقعة من جهة لا يثق بها أي متصفح، والأمر التالي يقرأ الشهادة الرئيسية Root Certificate التي ينشرها التوثيق نفسه:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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 فهو السطران المعتادان، وهما في المختبر:

```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 في nftables، وشهادة العميل في Authenticated Origin Pulls، واستعادة عنوان الزائر عبر real_ip، حيث تمر طلبات منطقتك في Cloudflare من الثلاث، ويتوقف طلب من منطقة حساب آخر عند الشهادة، ويتوقف المهاجم عند جدار الحماية](https://arabroot.io/content/images/2026/10/cloudflare-setup-02-origin-lock.webp)

قائمة العناوين تمنع من لا يمر عبر Cloudflare، والشهادة تمنع من يمر عبر حساب غير حسابك

### لماذا لا تكفي قواعد تكتبها مرة واحدة؟

الحل البسيط هو أن تنسخ النطاقات من [صفحة عناوين Cloudflare](https://www.cloudflare.com/ips/?ref=arabroot.io) إلى جدار الحماية مرة واحدة، أو أن تضيفها بحلقة تكرار كما فعلنا مع ufw في [المقال السابق](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/#lock-origin). وهذا يعمل في اليوم الأول، ولكن له ثلاث مشاكل تظهر مع الوقت: القائمة تتغير أحياناً، ويقول [توثيق عناوين Cloudflare](https://developers.cloudflare.com/fundamentals/concepts/cloudflare-ip-addresses/?ref=arabroot.io) إن النطاقات الجديدة تضاف إلى القائمة قبل أن تستخدم، فإذا لم تحدث قواعدك فسوف تحظر جزءاً من زوارك يوماً ما دون أن تعرف السبب. وإذا شغلت الحلقة مرة ثانية لتحدث القائمة تكررت القواعد ولم تحذف النطاقات القديمة. والأخطر أن تحميل القائمة قد يفشل أو يعيد صفحة خطأ، فيبني السكربت قائمة فارغة ويغلق الموقع على الجميع.

والحل الأنسب هو سكربت يحمل القائمتين الرسميتين [ips-v4](https://www.cloudflare.com/ips-v4?ref=arabroot.io) و[ips-v6](https://www.cloudflare.com/ips-v6?ref=arabroot.io)، ويتحقق من محتواهما، ثم يبني جدولاً كاملاً في nftables فيه مجموعتان Sets للعناوين، ويستبدل الجدول القديم بخطوة واحدة، ويبني في الوقت نفسه ملفات Nginx التي تعتمد على القائمة نفسها. وهذا هو السكربت كاملاً:

```bash
#!/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.

الآن نشغل السكربت في السيرفر الأصلي ونعرض السلسلة التي بناها:

```bash
docker exec lab-cf-origin cf-firewall.sh
docker exec lab-cf-origin nft list chain inet cloudflare_origin input
```

والمخرج سوف يكون كما يلي:

```text
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:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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 حزم أسقطها، وهو طريقة سريعة لتعرف أن هناك من يحاول الوصول إلى سيرفرك مباشرة.

والآن نكسر السكربت عمداً لنتأكد أنه لا يغلق الموقع عند الفشل، فنفصل السيرفر عن الإنترنت ونشغله مرة أخرى:

```bash
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'
```

والمخرج سوف يكون كما يلي:

```text
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`:

```text
@reboot   root sleep 30 && /usr/local/sbin/cf-firewall.sh
0 4 * * 1 root /usr/local/sbin/cf-firewall.sh
```

وإذا كان سيرفرك يستخدم ufw فلا يوجد تعارض، لأن ufw على Ubuntu يضيف قواعده في جداول أخرى، والحزمة يجب أن تقبلها كل الجداول لتصل، فيكفي أن يمنعها جدولنا. وأما أوامر ufw التي تؤدي الغرض نفسه بنفس فكرة الحلقة فهي في [المقال السابق](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/#lock-origin).

### إذا كان ال Reverse Proxy داخل Docker

هنا تأتي النقطة التي تفسد هذا الإعداد عند أغلب من يستخدم Docker. فالسلسلة `input` تمر بها الحزم الموجهة إلى السيرفر نفسه، أما الحزم الموجهة إلى منفذ نشرته في Docker بالشكل `443:443` فيحولها Docker إلى ال Container وتمر بسلسلة `forward`، فلا ترى قاعدتنا شيئاً منها، كما شرحنا في [شبكات Docker وأفضل الممارسات](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/). ولهذه الحالة ثلاثة حلول بالترتيب من الأبسط:

1. **Cloudflare Tunnel**: لا ينشر السيرفر أي منفذ للخارج، فلا يوجد ما تقفله أصلاً، وقد شرحناه في [قسم Tunnel](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/#tunnel).
2. **الحماية داخل Nginx نفسه**، وهي ما نجربه الآن.
3. **قواعد في السلسلة DOCKER-USER** مع مطابقة المنفذ الأصلي قبل التحويل، كما يشرح [توثيق Docker مع iptables](https://docs.docker.com/engine/network/firewall-iptables/?ref=arabroot.io)، أو جدول nftables منفصل بسلسلة لها نقطة الربط نفسها كما في [توثيق Docker مع nftables](https://docs.docker.com/engine/network/firewall-nftables/?ref=arabroot.io). وهذا الحل لم نجربه في المختبر لأنه يغير جدار الحماية في جهاز Docker نفسه.

ولكي نقلد حالة Docker نحذف جدول nftables من السيرفر، فيصبح كأن القاعدة غير موجودة، ثم يحاول المهاجم مرة أخرى:

```bash
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/
```

والمخرج سوف يكون كما يلي:

```text
origin says: your address is 198.51.100.66
```

وصل المهاجم مباشرة. والحل في Nginx أن نضيف إلى الإعداد ملف `cloudflare-geo.conf` الذي بناه السكربت، وهو يعطي المتغير `$from_cloudflare` القيمة 1 إذا جاء الاتصال من نطاق Cloudflare، ثم نغلق الاتصال دون رد بالرمز الخاص 444 لكل ما عدا ذلك:

```nginx
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` نعيد المحاولة من المهاجم ومن الزائر:

```text
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](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/#cloudflare). وما يهمنا هنا هو الخطأ الذي يجعل هذه الترويسة خطراً. فالحل البسيط أن تقول ل Nginx أن يصدق الترويسة من أي عنوان، لأن كتابة القائمة مملة:

```nginx
set_real_ip_from 0.0.0.0/0;
real_ip_header CF-Connecting-IP;
```

ونجرب ذلك قبل تشغيل السكربت، فيرسل المهاجم الطلب مباشرة إلى السيرفر ويكتب في الترويسة العنوان الذي يريد:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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`، وأوله وآخره:

```text
# 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;
```

وبعد تشغيل السكربت يظهر في سجل السيرفر سطران، الأول من محاولة المهاجم السابقة، والثاني من الزائر عبر الحافة:

```text
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](https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/?ref=arabroot.io) واختصاراً AOP، وهو متاح في كل الخطط، حيث تقدم Cloudflare شهادة عميل Client Certificate عند كل اتصال بسيرفرك، فيما يسمى mTLS، ويرفض سيرفرك أي اتصال لا يحمل شهادة موقعة من الجهة التي يثق بها.

ولهذه الخدمة ثلاثة مستويات بحسب التوثيق:

- **Global**: تستخدم شهادة تقدمها Cloudflare نفسها، وهي [مشتركة بين كل حسابات Cloudflare](https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/set-up/global/?ref=arabroot.io)، فتثبت فقط أن الطلب جاء من شبكة Cloudflare، أي أنها لا تحل مشكلة المهاجم الذي له حساب في Cloudflare.
- **Zone-level**: ترفع فيها شهادتك أنت، فتثبت أن الطلب جاء من منطقتك Zone في حسابك، وهي التي ننصح بها.
- **Per-hostname**: شهادة مختلفة لكل اسم فرعي Subdomain.

والأمر التالي يقرأ شهادة المستوى Global التي ينشرها التوثيق، لترى أنها شهادة واحدة لكل العملاء:

```bash
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
```

والمخرج سوف يكون كما يلي:

```text
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](https://developers.cloudflare.com/ssl/origin-configuration/authenticated-origin-pull/set-up/zone-level/?ref=arabroot.io) هي: تنشئ جهة توقيع CA خاصة بك، وتوقع بها شهادة عميل Leaf Certificate فيها `basicConstraints=CA:FALSE`، ثم ترفع شهادة العميل ومفتاحها إلى Cloudflare من صفحة SSL/TLS ثم Origin Server ثم Authenticated Origin Pulls، وتضع شهادة الجهة CA على سيرفرك. والأوامر التي استخدمناها في المختبر لإنشاء الجهة وشهادة العميل هي:

```bash
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:

```nginx
    ssl_client_certificate /etc/nginx/certs/aop-ca.pem;
    ssl_verify_client on;
```

ويوصي التوثيق بأن تبدأ بالقيمة `optional` ثم تفعل AOP في Cloudflare، ثم تتأكد من السجلات، وبعدها فقط تغيرها إلى `on`، والسبب أنك لو بدأت ب `on` قبل أن تفعل AOP في لوحة التحكم لرفض سيرفرك كل الزوار. والآن نجرب الحالتين في المختبر: حافة بوضع Full (strict) دون شهادة عميل، وهي تمثل منطقة حساب آخر، ثم حافة تقدم شهادة العميل، وملفها فيه هذان السطران زيادة على وضع Full (strict):

```nginx
        proxy_ssl_certificate     /etc/nginx/certs/aop-client.pem;
        proxy_ssl_certificate_key /etc/nginx/certs/aop-client.key;
```

والمخرج للحالتين سوف يكون كما يلي:

```text
<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، وإنما تحفظ الملفات الثابتة بحسب امتدادها، كما يشرح [توثيق السلوك الافتراضي](https://developers.cloudflare.com/cache/concepts/default-cache-behavior/?ref=arabroot.io). والمشكلة تبدأ عندما تكتب قاعدة [Cache Rule](https://developers.cloudflare.com/cache/how-to/cache-rules/?ref=arabroot.io) لتسرع الموقع كله، فتختار Eligible for cache لكل المسارات. والسؤال المهم هنا: ماذا يحدث لصفحة ترد بملف تعريف ارتباط Set-Cookie، مثل صفحة حساب المستخدم؟ والإجابة في [توثيق Set-Cookie](https://developers.cloudflare.com/cache/concepts/cache-behavior/?ref=arabroot.io):

- مع 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 التي يرسلها تطبيقك. لذلك اتبع القواعد التالية:

1. اجعل تطبيقك يرسل `Cache-Control: private, no-store` لكل صفحة خاصة بمستخدم، فالخطط Free وPro وBusiness تحترم هذه الترويسة دائماً بحسب [توثيق Cache-Control](https://developers.cloudflare.com/cache/concepts/cache-control/?ref=arabroot.io) ما لم تتجاهلها قاعدتك.
2. لا تستخدم Ignore cache-control header إلا في قاعدة تخص مسارات ثابتة فقط، مثل `/assets/` و`/images/`.
3. اكتب قاعدة Bypass cache قبل غيرها لمسارات مثل `/account` و`/admin` و`/api` ولكل طلب يحمل Cookie الجلسة.

وفي المختبر يرسل المسار `/account` الترويسة الصحيحة، والأمر التالي يتأكد منها:

```bash
docker exec lab-cf-visitor curl -sS --cacert /edge.pem -D - -o /dev/null https://www.example.com/account | grep -i 'HTTP/\|cache-control'
```

والمخرج سوف يكون كما يلي:

```text
HTTP/1.1 200 OK
Cache-Control: private, no-store
```

وعلى موقعك الحقيقي افتح الصفحة الخاصة بعد أي قاعدة جديدة، وتأكد أن الترويسة `cf-cache-status` فيها `BYPASS` أو `DYNAMIC` وليست `HIT`، كما ذكرنا في [المقال السابق](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/#cache-pitfalls).

## قائمة التحقق قبل أن تعتبر الإعداد منتهياً

| البند                                | كيف تتحقق منه                                                          | النتيجة الصحيحة                                                    |
| ------------------------------------ | ---------------------------------------------------------------------- | ------------------------------------------------------------------ |
| سجلات الويب 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                                           |

وبعد الانتهاء من المختبر نحذف كل ما أنشأناه بالاسم:

```bash
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 وبدائله](https://arabroot.io/start/#stage-edge) في خريطة التعلم، وفيها مقالات قادمة عن خادم DNS لنطاقاتك، والتخزين المؤقت أمام تطبيقك، وجدار حماية تطبيقات الويب WAF على سيرفرك، وبديل Cloudflare Tunnel. وهذه الأدلة التي يعتمد عليها ما شرحناه هنا:

[![](https://arabroot.io/content/images/2026/10/cloudflare-services-cover-cover.webp)دليلخدمات Cloudflare بكلمات بسيطة وبدائلها مفتوحة المصدرما الذي تقدمه كل خدمة من Cloudflare، وما المتاح منها مجاناً، وما البديل مفتوح المصدر الذي تشغله على سيرفرك.](https://arabroot.io/articles/%D8%AE%D8%AF%D9%85%D8%A7%D8%AA-cloudflare-%D9%88%D8%A8%D8%AF%D8%A7%D8%A6%D9%84%D9%87%D8%A7-%D9%85%D9%81%D8%AA%D9%88%D8%AD%D8%A9-%D8%A7%D9%84%D9%85%D8%B5%D8%AF%D8%B1/)[![](https://arabroot.io/content/images/2026/10/behind-proxy-cover-cover.webp)دليلكيف يرى تطبيقك عنوان الزائر الحقيقي خلف ال Reverse Proxyإذا كان تطبيقك يرى عنوان ال Proxy بدلاً من الزائر أو يولد روابط http، فهذا هو السبب والإعداد الصحيح لإطار عملك.](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/)[![](https://arabroot.io/content/images/2026/10/certificates-cover-cover.webp)دليلالشهادات الرقمية TLS للمبتدئين وصيغ ملفاتهاكيف يعرف المتصفح أنه يتحدث مع الموقع الحقيقي، وماذا يوجد داخل الشهادة، ولماذا تأتي ملفاتها بأكثر من صيغة.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/)[![](https://arabroot.io/content/images/2026/10/docker-networking-cover-cover.webp)دليلشبكات Docker وأفضل الممارسات مع Docker Composeلماذا يبقى المنفذ الذي تنشره في Compose مفتوحاً للإنترنت رغم ufw، وكيف تصل التطبيقات إلى بعضها عبر شبكات Docker دون نشر منافذ.](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/)[![](https://arabroot.io/content/images/2026/10/http-protection-cover-cover.webp)دليلحماية تطبيقات الويب بطبقات أمان في ال Reverse Proxyكلمة المرور وحدها لا تحمي تطبيقاً مكشوفاً على الإنترنت، لذلك نضع أمامه طبقات حماية في ال Reverse Proxy نفسه.](https://arabroot.io/articles/%D8%AD%D9%85%D8%A7%D9%8A%D8%A9-%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D8%A7%D8%AA-%D8%A7%D9%84%D9%88%D9%8A%D8%A8-%D8%B9%D9%84%D9%89-%D9%85%D8%B3%D8%AA%D9%88%D9%89-http/)

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- اجعل سجلات الويب وحدها 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 حقيقي.