الأدلة التقنية

DDNS: اسم ثابت لعنوان بيتك الذي يتغير

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

DDNS: اسم ثابت لعنوان بيتك الذي يتغير

لنفرض أنك أضفت سجل A للاسم home.example.com يشير إلى عنوان بيتك العام، وفتحت المنفذ في ال Router كما شرحنا في تشغيل خادمك من إنترنت المنزل، وعمل كل شيء من هاتفك وأنت خارج البيت، ثم في صباح اليوم التالي أو بعد انقطاع الكهرباء لم يعد الموقع يفتح، مع أن السيرفر يعمل وال Router يعمل ولم تغير أي إعداد. والسبب في أغلب الحالات أن مزود الخدمة ISP أعطى ال Router عنواناً عاماً جديداً، وأن سجل DNS ما زال يشير إلى العنوان القديم، وهذه بالضبط المشكلة التي جاء DNS الديناميكي Dynamic DNS، واختصاراً DDNS، ليحلها.

ولن نشرح هنا ما هو DNS ولا أنواع السجلات ومدة بقائها TTL، فقد شرحناها في شرح DNS وسجلاته للمبتدئين، وإنما نبدأ من سجل A وAAAA الذي تعرفه ونرى كيف نبقيه صحيحاً وحده. وقبل أن تبدأ تذكر أمراً واحداً سوف نعود إليه بالتفصيل: إذا كنت خلف CGNAT عند مزود الخدمة فإن DDNS لن يجعل خدمتك تصل من الإنترنت، والسبب أن العنوان العام الذي سوف يسجله ليس لك وحدك، كما شرحنا في قسم CGNAT من شرح NAT.

وسوف نناقش في هذا المقال ما يلي:

  • لماذا يتغير عنوانك العام، والحلول الأولى التي تخطر على البال ولماذا لا تكفي، ومتى لا يفيدك DDNS أصلاً.
  • كيف يعمل DDNS خطوة خطوة: اكتشاف العنوان، والمقارنة، واستدعاء واجهة المزود البرمجية API، ومدة بقاء السجل، وما يختلف في IPv6.
  • مزودو DDNS المجانيون DuckDNS وNo-IP وDynu وdeSEC وFreeDNS، واستخدام نطاقك الخاص عبر واجهة مزود DNS مثل Cloudflare، مع جدول للمقارنة.
  • أدوات التحديث: ما هو مدمج في ال Router، وddns-updater، وcloudflare-ddns، وddclient، وسكربت curl صغير تجدوله بنفسك.
  • تجربة عملية كاملة على جهازك مع مزود وهمي نشغله محلياً، نرى فيها التحديث يحدث فعلاً، ثم نكسر التوكن ونقرأ الخطأ.
  • حماية التوكن والواجهة، ومراقبة أن السجل يطابق عنوانك، وأشهر المشكلات وكيف تشخصها.

لماذا يتغير عنوانك العام؟

عندما يتصل ال Router بمزود الخدمة يحصل على عنوانه العام Public IP من المزود نفسه، وغالباً عبر DHCP أو عبر جلسة PPPoE في خطوط DSL وبعض خطوط الألياف، ولكل عنوان مدة إيجار Lease Time، فإذا انتهت المدة أو أعيد تشغيل ال Router أو انقطع الخط أو أجرى المزود صيانة على شبكته، فقد يعود ال Router بعنوان مختلف. وبعض المزودين يغيرون العنوان عمداً كل يوم أو كل أسبوع، والسبب أن العنوان الثابت Static IP خدمة يبيعونها منفصلة، وأن عناوين IPv4 قليلة فيعيدون توزيعها على المشتركين، وقد شرحنا شكل الخط والمزود من الداخل في كيف يصل الإنترنت إلى بيتك.

والنتيجة كما في الصورة التالية: ال Router والسيرفر يعملان، والعنوان الجديد صحيح، ولكن سجل DNS ما زال يعطي الزوار العنوان القديم، فيذهبون إلى لا أحد، أو إلى مشترك آخر أخذ عنوانك القديم:

مخطط يبين مزود الخدمة يغير العنوان العام لRouter المنزل من 203.0.113.10 إلى 198.51.100.77 بعد إعادة التشغيل، بينما يبقى سجل A للاسم home.example.com على العنوان القديم، فيذهب الزائر إلى العنوان القديم الذي صار لمشترك آخر
المزود غير العنوان، والسجل بقي على القديم

والحل الأول الذي يخطر على البال هو أن تعدل السجل يدوياً كلما تغير العنوان، وهذا الحل يفشل في أول مرة يتغير فيها العنوان في الثالثة فجراً أو وأنت مسافر، لأنك لن تعرف بالتغيير إلا عندما يشتكي أحد المستخدمين. والحل الثاني أن تطلب من المزود عنواناً ثابتاً، وهذا هو الحل الأنظف إن كان متاحاً بسعر مناسب، فلا يوجد شيء يتغير ولا توجد أداة تراقبه، ولكن كثيراً من المزودين لا يقدمونه للخطوط المنزلية، أو يقدمونه بسعر يقارب سعر سيرفر VPS صغير. والحل الثالث هو أن يقوم برنامج صغير في بيتك بما كنت سوف تقوم به يدوياً، أي أن يراقب العنوان ويعدل السجل بنفسه كلما تغير، وهذا هو DDNS.

كيف تعرف أن عنوانك يتغير؟

اسأل خدمة خارجية عن عنوانك العام مرة اليوم ومرة بعد إعادة تشغيل ال Router، فإذا اختلف العنوان فأنت تحتاج DDNS، وإذا بقي كما هو أسابيع فقد يكون عنوانك ثابتاً فعلياً، وفي هذه الحالة سجل A عادي يكفي. والأمر التالي يطلب عنوان IPv4 من ipify ثم عنوان IPv6 إن كان لديك:

curl -4 https://api.ipify.org; echo
curl -6 https://api6.ipify.org; echo

والمخرج سوف يكون شبيهاً بما يلي، مع عناوينك أنت:

198.51.100.77
2001:db8:20::77

وإذا فشل الأمر الثاني برسالة مثل Couldn't connect to server فليس لديك IPv6 من المزود، ويكفيك سجل A وحده.

متى لا يفيدك DDNS؟

DDNS يحل مشكلة واحدة فقط، وهي أن الاسم يتأخر عن العنوان، ولكنه لا يجعل عنوانك قابلاً للوصول من الإنترنت إذا لم يكن كذلك أصلاً. وأشهر حالة يخيب فيها هي CGNAT، حيث يعطي المزود ال Router عنواناً من النطاق 100.64.0.0/10 الذي خصصه RFC 6598 للمزودين، ثم يترجمه مرة ثانية إلى عنوان عام يتشاركه عدد من البيوت. وفي هذه الحالة سوف يكتشف المحدث العنوان العام المشترك ويسجله بشكل صحيح، ولكن الزائر الذي يصل إليه يتوقف عند جهاز المزود، لأنه لا توجد عنده قاعدة توجيه منفذ Port Forwarding لبيتك، كما في الصورة التالية:

مخطط يبين Router المنزل بعنوان WAN 100.64.0.2 خلف CGNAT عند المزود، والمحدث يرسل العنوان المشترك 203.0.113.20 إلى مزود DDNS فيصبح السجل صحيحاً، ولكن اتصال الزائر يتوقف عند المزود لأنه لا يوجد توجيه منفذ لهذا البيت
خلف CGNAT يصبح السجل صحيحاً ولا يصل الزائر

والاختبار بسيط: قارن عنوان WAN الذي تراه في صفحة ال Router بالعنوان الذي تعيده api.ipify.org، فإذا اختلفا، أو كان عنوان WAN يبدأ ب 100.64 إلى 100.127 أو كان عنواناً خاصاً مثل 192.168 أو 10، فأنت خلف NAT ثان، وقد شرحنا هذا الاختبار بالتفصيل في قسم اكتشاف CGNAT. وهناك حالات أخرى لا يفيد فيها DDNS وحده:

  • المزود يمنع المنافذ الواردة Inbound Ports: بعض المزودين يغلقون المنافذ 80 و443 و25 على الخطوط المنزلية، فيصل الاسم إلى العنوان الصحيح ولا يرد أحد.
  • Router خلف Router (Double NAT): إذا كان جهاز المزود يعمل Router وأمامه Router آخر لك، فعليك أن توجه المنفذ في الجهازين، أو أن تجعل جهاز المزود في وضع Bridge، وقد شرحناها في قسم Double NAT.
  • خدمة لا تحتاج اسماً عاماً أصلاً: إذا كنت تريد الوصول إلى سيرفرك وحدك من هاتفك، فشبكة خاصة مثل WireGuard أو نفق Tunnel أنسب من فتح منفذ للعالم.
⚠️
إذا كنت خلف CGNAT أو كان المزود يمنع المنافذ، فلا تضيع وقتك مع DDNS، والحل الأنسب هو نفق يخرج من بيتك إلى الإنترنت مثل Cloudflare Tunnel أو نفق عكسي إلى سيرفر VPS، أو أن تطلب من المزود عنواناً عاماً حقيقياً، وبعض المزودين يعطونه عند الطلب دون تكلفة.

كيف يعمل DDNS؟

DDNS هو برنامج يعمل داخل شبكتك يكتشف عنوانك العام بشكل دوري، ويعدل سجل DNS عند مزود DDNS أو عند مزود DNS الخاص بنطاقك كلما تغير العنوان، عن طريق طلب HTTPS يحمل توكن Token يثبت أنك صاحب السجل. والفكرة تشبه بطاقة تغيير العنوان التي تتركها في مكتب البريد عندما تنتقل من بيت إلى آخر، فالرسائل ما زالت تحمل اسمك، والمكتب يعرف أين يرسلها اليوم. والصورة التالية تبين دورة واحدة كاملة:

مخطط لدورة تحديث DDNS: المحدث في البيت يسأل خدمة خارجية عن عنوانه العام فتجيب 198.51.100.77، ثم يقارنه بما يعيده DNS وهو 203.0.113.10، فيرسل تحديثاً إلى واجهة المزود البرمجية مع التوكن، فينشر المزود السجل الجديد بمدة بقاء 60 ثانية، ويصل الزائر إلى العنوان الجديد
دورة واحدة: اسأل، قارن، حدث

في الصورة أعلاه لاحظ التالي:

  1. المحدث يسأل خدمة خارجية عن عنوانه، لأن السيرفر داخل البيت لا يعرف عنوانه العام، فعنوانه هو العنوان الخاص الذي أعطاه إياه ال Router.
  2. المحدث يقارن العنوان بما يعيده DNS أو بآخر عنوان حفظه، ولا يتصل بالمزود إلا إذا اختلفا، والسبب أن المزودين يضعون حدوداً لعدد الطلبات Rate Limits، وأن التحديث كل خمس دقائق دون تغيير يعامل عند بعضهم كإساءة استخدام.
  3. عند الاختلاف يرسل المحدث العنوان الجديد إلى واجهة المزود البرمجية API مع التوكن.
  4. المزود ينشر السجل الجديد على سيرفرات DNS الرسمية Authoritative لديه بمدة بقاء قصيرة.
  5. الزائر يسأل عن الاسم فيحصل على العنوان الجديد، ويصل إلى ال Router ثم إلى السيرفر عبر توجيه المنفذ.

كيف يعرف المحدث عنوانك العام؟

هناك ثلاث طرق، ولكل منها حالة تفشل فيها:

  • سؤال خدمة صدى Echo Service مثل api.ipify.org: الخدمة ترى العنوان الذي وصلها منه الطلب وتعيده لك، وهذه الطريقة الأدق لأنها ترى ما يراه الإنترنت فعلاً. ونقطة ضعفها أنك تثق بخدمة خارجية، فإذا تعطلت أو أعادت صفحة خطأ فقد يكتب محدث بسيط هذه الصفحة في السجل، لذلك تحقق دائماً أن الرد عنوان صحيح قبل أن ترسله، وهذا ما سوف تراه في السكربت لاحقاً.
  • قراءة عنوان WAN من ال Router: هذا ما تفعله ال Routers ذات العميل المدمج، وهو سريع ولا يحتاج خدمة خارجية، ولكنه يعطي عنواناً خاطئاً خلف CGNAT أو خلف Router آخر، لأن عنوان WAN عندها هو 100.64.x.x أو 192.168.x.x.
  • ترك المزود يكتشف العنوان: إذا لم ترسل العنوان في الطلب، فإن مزودين مثل DuckDNS وdeSEC يأخذون عنوان المصدر الذي وصلهم منه الطلب. وهذا مريح، ولكنه يعني أن الطلب يجب أن يخرج من شبكة البيت نفسها، وأن يخرج بنسخة IP التي تريد تحديثها، كما سيأتي في IPv6.

لماذا تكون مدة بقاء السجل TTL قصيرة؟

كل Resolver يسأل عن اسمك يحفظ الجواب طوال مدة بقاء السجل TTL ولا يسأل مرة أخرى قبل انتهائها، كما شرحنا في قسم التخزين المؤقت ومدة بقاء السجل. فإذا كانت المدة ساعة وتغير عنوانك، فقد يبقى بعض الزوار ساعة كاملة على العنوان القديم حتى بعد أن يحدث المحدث السجل. لذلك تكون المدة في سجلات DDNS قصيرة، 60 ثانية عند deSEC كما في توثيقه، وعند Cloudflare أقل قيمة لسجل DNS only هي 60 ثانية والخيار Auto يساوي 300 ثانية حسب توثيق TTL. والثمن أن ال Resolvers سوف تسأل عن اسمك أكثر، وهذا لا يهم سيرفراً منزلياً.

IPv6: لكل جهاز عنوانه، والبادئة تتغير أيضاً

في IPv4 يوجد عنوان عام واحد لكل البيت عند ال Router، أما في IPv6 فلا توجد ترجمة NAT في الغالب، ويعطي المزود بيتك بادئة Prefix كاملة مثل 2001:db8:20::/56، ويأخذ كل جهاز عنواناً عاماً خاصاً به من هذه البادئة، كما شرحنا في قسم IPv6 من شرح NAT. وهذا يغير ثلاثة أشياء في DDNS:

  • سجل AAAA يجب أن يحمل عنوان السيرفر نفسه وليس عنوان ال Router، لذلك يعمل محدث IPv6 على السيرفر الذي تريد الوصول إليه، أو يرسل عنوانه صراحة.
  • العناوين المؤقتة Temporary Addresses: أنظمة التشغيل تنشئ عناوين IPv6 مؤقتة تتغير دورياً لحماية الخصوصية حسب RFC 8981، وتستخدمها للاتصالات الخارجة، فإذا سألت خدمة صدى من السيرفر فقد تعيد لك العنوان المؤقت، فيتغير السجل كل يوم ثم يتوقف عن العمل عندما يلغي النظام هذا العنوان. والحل أن تستخدم العنوان الثابت للسيرفر، وأداة ddns-updater مثلاً لديها الخيار ipv6_suffix لهذا الغرض كما في توثيقها.
  • البادئة نفسها تتغير: كثير من المزودين يغيرون البادئة عند إعادة الاتصال كما يغيرون عنوان IPv4، فيتغير عنوان كل جهاز في البيت معاً، ولذلك تحتاج تحديث AAAA كما تحتاج تحديث A.

وقد يتساءل البعض: إذا كان كل جهاز يملك عنواناً عاماً، فهل يعني هذا أن السيرفر مكشوف دون توجيه منافذ؟ والإجابة على ذلك أن أغلب ال Routers المنزلية تمنع الاتصالات الواردة على IPv6 بجدار الحماية Firewall، فعليك أن تسمح بالمنفذ لهذا الجهاز تحديداً في إعدادات IPv6، وهي خطوة تشبه توجيه المنفذ ولكن دون ترجمة.

⚠️
إذا كان لديك سجل AAAA قديم والسجل A صحيح، فإن أغلب المتصفحات تحاول IPv6 أولاً وتنتظر قبل أن تعود إلى IPv4، فيبدو الموقع بطيئاً أو معطلاً عند بعض الزوار دون غيرهم. لذلك إما أن تحدث السجلين معاً، أو أن تحذف AAAA إذا لم تكن تحدثه.

مزودو DDNS: اسم مجاني أم نطاقك الخاص؟

قبل أن تختار أداة التحديث عليك أن تقرر أين يعيش الاسم، وهناك طريقان كما في الصورة التالية: اسم فرعي مجاني تحت نطاق مزود DDNS مثل myhome.duckdns.org، أو اسم تحت نطاقك الخاص مثل home.example.com تحدثه عبر واجهة مزود DNS الذي يدير نطاقك. وأداة التحديث نفسها، سواء كانت في ال Router أو في Container أو سكربت، ترسل طلب HTTPS مع توكن في الحالتين:

مخطط يبين ثلاثة أماكن يعمل فيها المحدث في البيت: نظام ال Router مثل OpenWrt، أو Container على سيرفر منزلي مثل ddns-updater، أو سكربت مع مؤقت، وكلها ترسل HTTPS مع توكن إلى أحد طريقين: اسم مجاني عند مزود DDNS مثل myhome.duckdns.org، أو نطاقك الخاص home.example.com عبر واجهة مزود DNS بتوكن مقيد بمنطقة واحدة، ويمكن ربطهما بسجل CNAME
أين يعمل المحدث، وإلى أين يرسل

والاسم المجاني أسرع للبدء ولا يحتاج شراء نطاق، ولكن الاسم ليس ملكك، فإذا أغلق المزود خدمته أو تغيرت شروطه أو تعطل نطاقه، فكل ما بنيته على هذا الاسم يتعطل معه، والقصة التالية حدثت فعلاً.

📌
في 30 يونيو 2014 حصلت Microsoft على أمر من محكمة أمريكية واستولت على 22 من أكثر نطاقات No-IP استخداماً، بحجة أن بعض الأسماء الفرعية فيها استخدمها مطورو برمجيات خبيثة، فتوقف قرابة 5 ملايين اسم وأصبح 1.8 مليون موقع وجهاز لعملاء أبرياء غير قابل للوصول كما في تفاصيل No-IP عن الحادثة، وقالت No-IP في بيانها يوم الحادثة إن Microsoft لم تتواصل معها قبل ذلك. وانتهى الأمر بتسوية في 10 يوليو 2014 وعادت النطاقات، ولكن من كان يعتمد على هذه الأسماء بقي أياماً دون خدمة لسبب لا علاقة له به. والدرس أن الاسم الذي تحت نطاق غيرك يتأثر بكل ما يحدث لهذا النطاق، فإذا كانت الخدمة مهمة فاجعل الاسم تحت نطاقك.

وفيما يلي المزودون الأكثر استخداماً، وكل المعلومات من صفحاتهم الرسمية وقت كتابة المقال، فراجعها قبل التسجيل لأن الخطط المجانية تتغير.

DuckDNS: أبسط بداية

DuckDNS خدمة مجانية تعطيك أسماء فرعية تحت duckdns.org، ويديرها كما تقول صفحتها مهندسان، وتستضيفها على AWS. والتحديث طلب GET واحد يحمل الاسم والتوكن، والعنوان اختياري، فإذا لم ترسله يكتشف المزود عنوان IPv4 من الطلب نفسه كما في مواصفات الواجهة. والمثال التالي يبين شكل الطلب، والتوكن فيه مثال فقط:

curl -fsS "https://www.duckdns.org/update?domains=myhome&token=YOUR-DUCKDNS-TOKEN&ip="

والرد حسب التوثيق كلمة OK عند النجاح وKO عند الفشل، وإذا أضفت &verbose=true يعيد العنوان المسجل وكلمة UPDATED أو NOCHANGE. وفي الإعداد أعلاه لاحظ التالي:

  • التوكن في DuckDNS واحد للحساب كله، أي أن من يحصل عليه يستطيع تعديل كل أسمائك، ولا توجد طريقة لتقييده باسم واحد.
  • DuckDNS لا يكتشف IPv6 تلقائياً كما يذكر في الأسئلة الشائعة، فعليك أن ترسله في المعامل ipv6.
  • يمكن استخدام نطاقك الخاص معه بسجل CNAME يشير إلى اسمك في DuckDNS، وهذا مذكور في الأسئلة الشائعة نفسها.

No-IP: موجود في أغلب ال Routers، مع تأكيد شهري

الخطة المجانية في No-IP تعطيك اسماً واحداً Hostname تحت نطاقات No-IP، واستخدام نطاقك الخاص خدمة مدفوعة. وأهم ما يجب أن تعرفه أن الاسم المجاني يجب تأكيده كل 30 يوماً، فبعد 23 يوماً تصلك رسالة تأكيد ويظهر زر التأكيد في اللوحة 7 أيام، وإذا لم تؤكد ينتقل الاسم إلى فترة استرداد Redemption مدتها 14 يوماً ثم يحذف ويصبح متاحاً لغيرك، كما في قاعدة المعرفة لديهم. وميزة No-IP أن عميله مدمج في أغلب ال Routers والكاميرات، وهذا هو سبب انتشاره، ولكن التأكيد الشهري يجعله غير مناسب لخدمة تريدها أن تعمل دون أن تتذكرها.

Dynu: نطاقك الخاص في الخطة المجانية

الخطة المجانية في Dynu تسمح بأربعة أسماء مجانية تحت نطاقاتهم، وبأربعة نطاقات خاصة بك، أي أنك تستطيع أن تضع نطاقك عندهم وتحدثه دون أن تدفع. والتحديث عبر بروتوكول IP Update المتوافق مع dyndns2، ويقبل myip لعنوان IPv4 وmyipv6 لعنوان IPv6، ولذلك تعمل معه أغلب ال Routers والأدوات التي تدعم dyndns2.

deSEC: جمعية غير ربحية مع DNSSEC

deSEC خدمة DNS مجانية تديرها جمعية غير ربحية مسجلة في برلين اسمها deSEC e.V.، وتعطيك اسماً تحت dedyn.io أو تستضيف نطاقك الخاص، وتوقع كل السجلات ب DNSSEC، وكود الخدمة مفتوح المصدر. والتحديث عبر الخادم update.dedyn.io، ومدة بقاء السجل 60 ثانية، والمثال التالي يرسل عنوان IPv4 ويبقي عنوان IPv6 كما هو:

curl -fsS --user "myhome.dedyn.io:YOUR-DESEC-TOKEN" \
  "https://update.dedyn.io/?myipv4=198.51.100.77&myipv6=preserve"

وفي الإعداد أعلاه لاحظ التالي، وكله من توثيق واجهة التحديث:

  • التوكن يرسل في ترويسة Basic Authentication وليس في الرابط، والسبب أن deSEC يحفظ روابط الطلبات في سجلاته Logs بضعة أيام، فإذا وضعت التوكن في الرابط سوف يبقى في هذه السجلات نصاً واضحاً.
  • إذا لم ترسل myipv4 وكان الطلب نفسه خرج عبر IPv6، فإن deSEC يحذف سجل IPv4، ولهذا نرسل العنوان صراحة أو نستخدم preserve.
  • حد التحديث عبر dynDNS طلبان كل دقيقتين لكل نطاق حسب صفحة حدود الطلبات، فلا تحدث إلا عند التغيير.

FreeDNS: أسماء مشتركة أو نطاقك الخاص

FreeDNS من afraid.org يعطيك أسماء فرعية تحت نطاقات تبرع بها أعضاء آخرون Shared Domains، أو يستضيف نطاقك الخاص مجاناً، والعضوية المدفوعة تضيف أسماء أكثر وميزات أخرى. والتحديث برابط خاص بكل سجل، فطلب واحد إلى sync.afraid.org يحدث IPv4، والطلب نفسه إلى v6.sync.afraid.org يحدث IPv6، كما في المثال على صفحتهم الرئيسية:

curl -fsS "https://sync.afraid.org/u/YOUR-UPDATE-TOKEN/"
curl -fsS "https://v6.sync.afraid.org/u/YOUR-UPDATE-TOKEN/"

والأسماء تحت النطاقات المشتركة يملك نطاقها عضو آخر، ولذلك توصي الأسئلة الشائعة لديهم باستخدام نطاقك الخاص إذا أردت أن يبقى الاسم مدة طويلة تحت سيطرتك.

نطاقك الخاص عبر واجهة مزود DNS: Cloudflare مثالاً

إذا كان لديك نطاق، فالحل الأنسب في رأينا أن يبقى عند مزود DNS الذي تستخدمه، وأن تحدث سجلاً واحداً فيه عبر الواجهة البرمجية. وأغلب المزودين لديهم واجهة لذلك، مثل Cloudflare وdeSEC وHetzner وDigitalOcean وAmazon Route 53 وPorkbun، وإذا كنت تدير سيرفر DNS خاصاً بك مثل BIND أو PowerDNS فهناك معيار التحديث الديناميكي RFC 2136 الذي تدعمه أداة nsupdate وddclient. وقد شرحنا خدمة DNS في Cloudflare في خدمات Cloudflare وبدائلها، وما يهمنا هنا هو التوكن.

الطريقة البسيطة هي أن تستخدم المفتاح العام Global API Key، وهذا المفتاح يملك كل صلاحيات حسابك على كل النطاقات، فإذا تسرب من جهاز في البيت يستطيع صاحبه أن يحذف نطاقاتك أو يغير سجلات البريد فيها. والطريقة الصحيحة أن تنشئ توكن API Token من القالب Edit zone DNS، وأن تقيده في قسم Zone Resources بالمنطقة Zone التي فيها الاسم وحدها، فتكون صلاحيته Zone › DNS › Edit على example.com فقط، وأي طلب على منطقة أخرى يرفض. وشكل الطلبين اللذين يحتاجهما أي محدث هو كما يلي، والقيم فيهما أمثلة:

curl -fsS -H "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records?type=A&name=home.example.com"

curl -fsS -X PATCH -H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/dns_records/$RECORD_ID" \
  --data '{"content":"198.51.100.77"}'

الطلب الأول يبحث عن السجل بنوعه واسمه ويعيد معرفه id، والطلب الثاني يعدل محتوى السجل بالطريقة PATCH، أي أنه يغير العنوان فقط ويترك باقي الإعدادات مثل TTL وحالة ال Proxy كما هي. وحد الطلبات في Cloudflare هو 1200 طلب كل خمس دقائق لكل مستخدم كما في صفحة حدود الواجهة، وهو حد لن يصل إليه محدث يعمل بشكل صحيح.

💡
صفحة إنشاء التوكن في Cloudflare فيها خيار Client IP Address Filtering لتقييد التوكن بعناوين محددة، وهو خيار ممتاز لسيرفر في مركز بيانات، ولكنه لا يناسب DDNS، والسبب أن عنوانك هو بالضبط ما يتغير، فإذا قيدت التوكن بعنوانك الحالي فسوف يرفض أول تحديث بعد تغير العنوان.

وإذا كان السجل في Cloudflare في وضع Proxied، أي السحابة البرتقالية، فإن الزوار يرون عناوين Cloudflare وليس عنوان بيتك، وهذا يخفي عنوانك، ولكنه يغير طريقة المقارنة، فالأداة لا تستطيع أن تسأل DNS عن عنوانك الحالي لأن الجواب عنوان Cloudflare، ولذلك تعتمد على آخر عنوان حفظته، وقد شرحنا الفرق بين الوضعين في قسم Proxied أم DNS only.

المقارنة بين المزودين

المزودالاسم في الخطة المجانيةنطاقك الخاص مجاناًتأكيد دوريIPv6التوكن
DuckDNSأسماء تحت duckdns.orgعبر CNAME فقطلا يوجديرسل يدوياً، لا يكتشف تلقائياًتوكن واحد للحساب كله
No-IPاسم واحدلا، خدمة مدفوعةكل 30 يوماًلا تذكره صفحة الخطة المجانيةاسم المستخدم وكلمة المرور أو مفتاح DDNS
Dynu4 أسماءنعم، 4 نطاقاتلا تذكره صفحة الخططmyipv6اسم المستخدم وكلمة المرور
deSECاسم تحت dedyn.ioنعم، مع DNSSECلا يوجدmyipv6 أو update6.dedyn.ioتوكن لكل استخدام، يمكن إنشاء أكثر من واحد
FreeDNSأسماء تحت نطاقات مشتركةنعملا تذكره الصفحةv6.sync.afraid.orgرابط خاص لكل سجل
Cloudflareلا يوجد، يحتاج نطاقاًنعم، الخطة المجانيةلا يوجدسجل AAAA عاديAPI Token مقيد بمنطقة واحدة وصلاحية DNS Edit

والخلاصة العملية: إذا لم يكن لديك نطاق وأردت أن تبدأ اليوم فابدأ ب DuckDNS أو deSEC، وإذا كان لديك نطاق فاجعل الاسم تحته وحدثه عبر مزود DNS الذي تستخدمه، وإذا كان ال Router لا يدعم إلا No-IP فتذكر التأكيد الشهري أو انقل التحديث إلى أداة على السيرفر.

أدوات التحديث: من ال Router إلى سكربت صغير

المزود يحدد أين يعيش الاسم، والأداة تحدد من يراقب العنوان، ويمكن أن تجمع أي أداة مع أغلب المزودين. وفيما يلي الخيارات من الأبسط إلى الأكثر تحكماً.

العميل المدمج في ال Router

أغلب ال Routers المنزلية فيها صفحة DDNS تختار فيها المزود من قائمة ثابتة وتكتب الاسم والتوكن، وهذا أبسط خيار لأنه لا يحتاج جهازاً يعمل طوال الوقت، وال Router يعرف بتغير العنوان قبل أي جهاز آخر. ونقاط ضعفه أن القائمة محدودة بما وضعه المصنع، وأن كثيراً منها يقرأ عنوان WAN فيرسل عنواناً خاطئاً خلف CGNAT، وأن التوكن يبقى في جهاز قد لا يحصل على تحديثات أمنية. أما إذا كان ال Router يعمل بنظام OpenWrt، فحزمة ddns-scripts مع واجهة luci-app-ddns تدعم عدداً كبيراً من المزودين ورابطاً مخصصاً Custom URL، وتسمح باكتشاف العنوان من خدمة خارجية بدل واجهة WAN، كما في توثيق OpenWrt.

ddns-updater: Container واحد لعشرات المزودين

ddns-updater أداة مكتوبة بلغة Go برخصة MIT، وآخر إصدار لها v2.10.0 صدر في مايو 2026 والمستودع نشط، وتدعم أكثر من 50 مزوداً منها كل المزودين أعلاه، ولها واجهة ويب Web UI صغيرة تعرض حالة كل سجل وتاريخ العناوين، وفحص صحة Healthcheck يتأكد من أن السجلات تطابق عنوانك، وإشعارات عبر Shoutrrr. وفي كل دورة تسأل الأداة عن عنوانك، ثم تسأل DNS عن السجل، ولا تتصل بالمزود إلا إذا اختلفا، وبالتالي لا تستهلك حدود الطلبات، كما يشرح ملف README. وتحتاج Docker مثبتاً كما في دليل تثبيت Docker.

الآن سوف نقوم بإنشاء مجلد الأداة، والملف data/config.json يجب أن يكون مملوكاً للمستخدم 1000 لأن الأداة تعمل داخل ال Container بهذا المستخدم:

mkdir -p ~/ddns-updater/data && cd ~/ddns-updater
touch data/config.json
sudo chown -R 1000 data && sudo chmod 600 data/config.json

والملف data/config.json سوف يكون كما يلي، وفيه سجل في Cloudflare وسجل في DuckDNS، والقيم الكبيرة أمثلة تضع مكانها قيمك:

{
  "settings": [
    {
      "provider": "cloudflare",
      "zone_identifier": "YOUR-ZONE-ID",
      "domain": "home.example.com",
      "ttl": 60,
      "token": "YOUR-CLOUDFLARE-API-TOKEN",
      "ip_version": "ipv4"
    },
    {
      "provider": "duckdns",
      "domain": "myhome.duckdns.org",
      "token": "YOUR-DUCKDNS-TOKEN",
      "ip_version": "ipv4"
    }
  ]
}

وملف docker-compose.yml بإصدار مثبت Pinned كما يلي:

services:
  ddns-updater:
    image: qmcgaw/ddns-updater:v2.10.0
    container_name: ddns-updater
    restart: unless-stopped
    environment:
      - PERIOD=5m
      - UPDATE_COOLDOWN_PERIOD=5m
      - LOG_LEVEL=info
      - TZ=UTC
    volumes:
      - ./data:/updater/data
    ports:
      - 127.0.0.1:8000:8000

في الإعداد أعلاه لاحظ التالي:

  • الإصدار مثبت على v2.10.0 وليس latest، حتى لا يتغير سلوك الأداة دون أن تقرر أنت.
  • PERIOD=5m يعني أن الأداة تفحص العنوان كل خمس دقائق، وUPDATE_COOLDOWN_PERIOD يمنع تحديث السجل نفسه أكثر من مرة في هذه المدة، حتى لو تذبذب العنوان.
  • الواجهة منشورة على 127.0.0.1 فقط، والسبب أن واجهة ddns-updater لا يوجد فيها تسجيل دخول، فإذا نشرتها على كل الواجهات يستطيع أي جهاز في الشبكة أن يرى أسماءك وعناوينك. وإذا أردت فتحها من جهاز آخر فاستخدم نفق SSH أو شبكة خاصة كما في التوجيه المحلي في أنفاق SSH.
  • الأداة تكتشف العنوان افتراضياً من عدة خدمات صدى عبر HTTPS وDNS وتتنقل بينها، حتى لا يحظرها أحدها بسبب كثرة الطلبات.
docker compose up -d
docker compose logs -f ddns-updater

وسوف ترى في السجل لكل سجل سطراً يقارن العنوانين، ثم سطر التحديث عند الاختلاف، وسوف نرى هذه الأسطر حقيقية في التجربة العملية.

cloudflare-ddns: لمن نطاقه في Cloudflare فقط

إذا بحثت عن DDNS مع Cloudflare فسوف تجد في كثير من الشروحات القديمة الأداة oznu/cloudflare-ddns، وهذه الأداة متوقفة، فمستودعها oznu/docker-cloudflare-ddns مؤرشف Archived على GitHub وآخر تعديل فيه كان في أغسطس 2022، لذلك لا ينصح باستخدامها في إعداد جديد. والبديل المعروف هو favonia/cloudflare-ddns، وهي أداة نشطة برخصة Apache 2.0 وآخر إصدار لها 1.17.1 صدر في سبتمبر 2026، وتعمل مع Cloudflare وحده، ويذكر ملف README في قسم الانتقال من oznu أن الأداة القديمة كانت تكتشف العنوان عبر DNS غير المحمي، مما يسهل تزوير الجواب. والإعداد التالي مأخوذ من ملف README مع تثبيت الإصدار:

services:
  cloudflare-ddns:
    image: favonia/cloudflare-ddns:1.17.1
    network_mode: host
    restart: always
    user: "1000:1000"
    read_only: true
    cap_drop: [all]
    security_opt: [no-new-privileges:true]
    environment:
      - CLOUDFLARE_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
      - DOMAINS=home.example.com
      - PROXIED=false

وفي الإعداد أعلاه لاحظ أن network_mode: host موجود لتسهيل اكتشاف IPv6، لأن شبكة Docker الافتراضية لا تعطي ال Container عنوان IPv6، فإذا كنت تحتاج IPv4 فقط يمكنك حذفه. والأسطر user وread_only وcap_drop وno-new-privileges تشغل الأداة دون صلاحيات root ودون قدرة على الكتابة في نظام الملفات، وهذا مناسب لأداة تحمل توكن.

ddclient: الأداة الكلاسيكية

ddclient أداة مكتوبة بلغة Perl برخصة GPL 2.0، موجودة في مستودعات أغلب التوزيعات، وتدعم عشرات المزودين وبروتوكولات منها dyndns2 وCloudflare وDuckDNS وdeSEC وnsupdate. وآخر إصدار لها هو 4.0.0 الصادر في يناير 2025، والمستودع ما زال يستقبل تعديلات. والإعداد في ملف واحد /etc/ddclient/ddclient.conf، وسوف نجربه في التجربة العملية مع بروتوكول dyndns2، وأمثلة المزودين الآخرين موجودة في ملف الإعداد النموذجي.

⚠️
ملف الإعداد النموذجي في ddclient 4.0.0 يذكر أن توكن Cloudflare يحتاج صلاحية Zone › Zone › Read إضافة إلى Zone › DNS › Edit، وأن يكون على كل المناطق All zones. وهذا أوسع مما يحتاجه تحديث سجل واحد، لذلك إذا كان نطاقك في Cloudflare فنفضل ddns-updater أو cloudflare-ddns أو السكربت التالي، لأنها تعمل بتوكن مقيد بمنطقة واحدة.

سكربت curl مع cron أو systemd timer

إذا كان لديك سجل واحد ولا تريد أداة إضافية، فسكربت صغير يكفي، بشرط أن يتعامل مع الحالات التي تكسر السكربت البسيط: خدمة الصدى تعيد صفحة خطأ، والسجل غير موجود، والتوكن خاطئ، والتحديث كل خمس دقائق دون تغيير. احفظ أولاً الأسرار Secrets في ملف لا يقرؤه غير root:

sudo apt install -y curl jq
sudo tee /etc/cf-ddns.env >/dev/null <<'EOF'
CF_API_TOKEN=YOUR-CLOUDFLARE-API-TOKEN
CF_ZONE_ID=0123456789abcdef0123456789abcdef
CF_RECORD_NAME=home.example.com
EOF
sudo chmod 600 /etc/cf-ddns.env

والسكربت /usr/local/bin/cf-ddns.sh سوف يكون كما يلي، وهو يحدث سجل A افتراضياً، أو سجل AAAA إذا شغلته مع CF_RECORD_TYPE=AAAA:

#!/usr/bin/env bash
# Updates one A or AAAA record on Cloudflare when the public address changes.
set -euo pipefail
source /etc/cf-ddns.env
TYPE=${CF_RECORD_TYPE:-A}
API=${CF_API:-https://api.cloudflare.com/client/v4}/zones/$CF_ZONE_ID/dns_records
STATE=/var/lib/cf-ddns.$TYPE
if [[ $TYPE == A ]]; then
  IP=$(curl -4 -fsS --max-time 10 "${IP_URL:-https://api.ipify.org}")
  [[ $IP =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]] || { echo "bad IPv4: $IP" >&2; exit 1; }
else
  IP=$(curl -6 -fsS --max-time 10 "${IP_URL:-https://api6.ipify.org}")
  [[ $IP == *:* && $IP =~ ^[0-9a-fA-F:]+$ ]] || { echo "bad IPv6: $IP" >&2; exit 1; }
fi
[[ -f $STATE && $(cat "$STATE") == "$IP" ]] && exit 0
AUTH=(-H "Authorization: Bearer $CF_API_TOKEN" -H "Content-Type: application/json")
ID=$(curl -fsS "${AUTH[@]}" "$API?type=$TYPE&name=$CF_RECORD_NAME" | jq -r '.result[0].id')
[[ -n $ID && $ID != null ]] || { echo "record $TYPE $CF_RECORD_NAME not found" >&2; exit 1; }
curl -fsS -X PATCH "${AUTH[@]}" "$API/$ID" --data "{\"content\":\"$IP\"}" | jq -e '.success' >/dev/null
echo "$IP" > "$STATE"
logger -s -t cf-ddns "updated $TYPE $CF_RECORD_NAME to $IP"

في السكربت أعلاه لاحظ التالي:

  • يتحقق السكربت من أن ما أعادته خدمة الصدى عنوان IPv4 أو IPv6 صحيح قبل أن يرسله، فإذا أعادت صفحة خطأ أو نصاً فارغاً يتوقف ولا يكتب شيئاً في السجل.
  • يحفظ آخر عنوان أرسله في ملف حالة لكل نوع، ولا يتصل بواجهة Cloudflare إلا إذا تغير العنوان، فإذا شغلته كل خمس دقائق فإنه يتصل بخدمة الصدى فقط في أغلب المرات.
  • الخيار -f في curl يجعل أي رد خطأ من الواجهة يوقف السكربت قبل أن يحفظ الحالة، فيعيد المحاولة في الدورة التالية تلقائياً.
  • المتغيران CF_API وIP_URL لهما قيم افتراضية هي عناوين Cloudflare وipify الحقيقية، ووجودهما يسمح لنا باختبار السكربت على مزود وهمي في التجربة العملية دون تعديله.

الآن اجعل السكربت قابلاً للتنفيذ، ثم جدوله بإحدى طريقتين. الأولى سطر في cron يعمل كل خمس دقائق:

sudo chmod 755 /usr/local/bin/cf-ddns.sh
echo '*/5 * * * * root /usr/local/bin/cf-ddns.sh' | sudo tee /etc/cron.d/cf-ddns

والثانية مؤقت systemd timer، وميزته أن مخرج السكربت وأخطاءه تذهب إلى سجل journal مع حالة كل تشغيل. ملف الخدمة /etc/systemd/system/cf-ddns.service:

[Unit]
Description=Update the DDNS record on Cloudflare
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/cf-ddns.sh

وملف المؤقت /etc/systemd/system/cf-ddns.timer:

[Unit]
Description=Run cf-ddns every 5 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now cf-ddns.timer
systemctl list-timers cf-ddns.timer
journalctl -u cf-ddns.service -n 20

ولا تستخدم الطريقتين معاً، والسبب أن السكربتين سوف يعملان في الوقت نفسه ويكتبان ملف الحالة نفسه.

تجربة عملية: مزود DDNS وهمي على جهازك

لا يمكن أن نجرب DDNS على مزود حقيقي دون حساب وتوكن ونطاق، ولا نريد أن نغير سجلات حقيقية ونحن نتعلم، لذلك سوف نبني كل شيء على جهازك داخل Docker: مزوداً وهمياً يكتشف العنوان ويستقبل التحديث، وسيرفر DNS يعيد السجل، ثم نشغل ddns-updater والسكربت وddclient عليه ونرى التحديث يحدث فعلاً. والتجربة تتكون من أربعة Containers:

  • mock: برنامج Python صغير يقوم بدور ثلاثة أشياء، خدمة صدى تعيد عنواناً عاماً نتحكم فيه لنحاكي تغيير المزود للعنوان، وواجهة تحديث بأسلوب dyndns2 التي تستخدمها ال Routers وأغلب المزودين، وجزء صغير من واجهة Cloudflare يكفي لاختبار السكربت.
  • dns: سيرفر dnsmasq يجيب عن home.example.com من ملف يكتبه mock عند كل تحديث، بمدة بقاء 60 ثانية.
  • updater: الأداة ddns-updater بالإصدار المثبت نفسه، ومعها مزود مخصص Custom يشير إلى mock.
  • client: Container فيه curl وjq وdig وddclient لنشغل منه السكربت والفحوص.

كل العناوين في التجربة من النطاقات المخصصة للتوثيق في RFC 5737 وRFC 3849، ولا يخرج أي طلب منها إلى مزود حقيقي.

إنشاء الملفات

المزود المخصص في ddns-updater يقبل روابط HTTPS فقط، لذلك نبدأ بإنشاء جهة شهادات CA صغيرة للتجربة وشهادة للاسمين api.ddns.test وip.ddns.test، والنطاق .test محجوز للاختبار فلا يتعارض مع أي اسم حقيقي:

mkdir -p ~/ddns-lab/{certs,data,work} && cd ~/ddns-lab
openssl req -x509 -newkey rsa:2048 -nodes -days 30 -subj "/CN=lab-ddns CA" -keyout certs/ca.key -out certs/ca.crt
openssl req -newkey rsa:2048 -nodes -subj "/CN=api.ddns.test" -keyout certs/server.key -out certs/server.csr
printf "subjectAltName=DNS:api.ddns.test,DNS:ip.ddns.test\n" > certs/san.ext
openssl x509 -req -in certs/server.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -days 30 -extfile certs/san.ext -out certs/server.crt
sudo chown -R 1000 data

والمزود الوهمي mock.py سوف يكون كما يلي:

# A fake DDNS provider for testing: an IP echo service, a dyndns2 update endpoint and a small part of the
# Cloudflare DNS API. Each accepted update rewrites /hosts/lab.hosts, which dnsmasq serves as the DNS answer.
import base64, json, os, socket, ssl, time, zlib
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse, parse_qs

TOKEN = os.environ['LAB_TOKEN']
DB = '/state/records.json'

def log(msg):
    print(time.strftime('%H:%M:%S'), msg, flush=True)

def load():
    return json.load(open(DB)) if os.path.exists(DB) else {'home.example.com': {'A': '203.0.113.10', 'AAAA': '2001:db8:10::10'}}

def save(rec):
    json.dump(rec, open(DB, 'w'))
    with open('/hosts/lab.hosts.tmp', 'w') as f:
        f.writelines(f'{ip} {name}\n' for name, t in rec.items() for ip in t.values())
    os.replace('/hosts/lab.hosts.tmp', '/hosts/lab.hosts')

def set_ip(name, ip):
    rec = load(); t = rec.setdefault(name, {}); kind = 'AAAA' if ':' in ip else 'A'
    if t.get(kind) == ip:
        return False
    log(f'  record {name} {kind}: {t.get(kind)} -> {ip}')
    t[kind] = ip; save(rec)
    return True

def rid(name, kind):
    return f'{zlib.crc32((name + kind).encode()):08x}'

class Handler(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def reply(self, code, body):
        data = (body if isinstance(body, str) else json.dumps(body)).encode()
        self.send_response(code); self.send_header('Content-Length', str(len(data))); self.end_headers()
        self.wfile.write(data)

    def bearer_ok(self):
        if self.headers.get('Authorization') == f'Bearer {TOKEN}':
            return True
        log(f'{self.command} {urlparse(self.path).path} -> 403 Authentication error')
        self.reply(403, {'success': False, 'errors': [{'code': 10000, 'message': 'Authentication error'}]})
        return False

    def do_GET(self):
        url = urlparse(self.path); q = {k: v[0] for k, v in parse_qs(url.query).items()}
        if url.path in ('/ip', '/ip6'):                       # what is my public address?
            ip = open('/state/' + ('ip4' if url.path == '/ip' else 'ip6')).read().strip()
            log(f'GET {url.path} -> {ip}')
            return self.reply(200, ip + '\n')
        if url.path == '/nic/update':                         # dyndns2: routers, ddclient, ddns-updater
            auth = self.headers.get('Authorization', '')
            secret = q.get('token') or (base64.b64decode(auth[6:]).decode().partition(':')[2] if auth.startswith('Basic ') else '')
            log(f'GET /nic/update hostname={q.get("hostname")} myip={q.get("myip")} agent="{self.headers.get("User-Agent")}"')
            if secret != TOKEN:
                log('  -> badauth')
                return self.reply(401, 'badauth\n')
            ip = q['myip']
            answer = ('good ' if set_ip(q['hostname'], ip) else 'nochg ') + ip
            log(f'  -> {answer}')
            return self.reply(200, answer + '\n')
        if url.path.endswith('/dns_records'):                 # Cloudflare: list records
            if not self.bearer_ok():
                return
            res = [{'id': rid(n, k), 'type': k, 'name': n, 'content': v, 'ttl': 60}
                   for n, t in load().items() for k, v in t.items() if q.get('type') == k and q.get('name') == n]
            log(f'GET {url.path}?type={q.get("type")}&name={q.get("name")} -> {len(res)} record(s)')
            return self.reply(200, {'success': True, 'errors': [], 'result': res})
        self.reply(404, 'not found\n')

    def do_PATCH(self):                                       # Cloudflare: change one record
        if not self.bearer_ok():
            return
        body = json.loads(self.rfile.read(int(self.headers['Content-Length'])))
        for n, t in load().items():
            for k in t:
                if self.path.endswith('/' + rid(n, k)):
                    log(f'PATCH {self.path} {json.dumps(body)}')
                    set_ip(n, body['content'])
                    return self.reply(200, {'success': True, 'errors': [], 'result': {'name': n, 'type': k, 'content': body['content']}})
        self.reply(404, {'success': False, 'errors': [{'code': 81044, 'message': 'Record does not exist.'}]})

class DualStack(ThreadingHTTPServer):
    address_family = socket.AF_INET6
    def server_bind(self):
        self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
        super().server_bind()

for f, v in (('/state/ip4', '203.0.113.10'), ('/state/ip6', '2001:db8:10::10')):
    if not os.path.exists(f):
        open(f, 'w').write(v + '\n')
save(load())
server = DualStack(('::', 443), Handler)
tls = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER); tls.load_cert_chain('/certs/server.crt', '/certs/server.key')
server.socket = tls.wrap_socket(server.socket, server_side=True)
log('fake provider listening on :443')
server.serve_forever()

في الكود أعلاه لاحظ التالي:

  • المسار /ip يعيد محتوى الملف /state/ip4، فإذا غيرنا هذا الملف نكون قد حاكينا مزود الخدمة وهو يعطي ال Router عنواناً جديداً.
  • المسار /nic/update يقبل التوكن في الرابط أو في ترويسة Basic Authentication، ويرد good عند التغيير وnochg عندما يكون العنوان كما هو وbadauth مع الرمز 401 عندما يكون التوكن خاطئاً، وهي ردود بروتوكول dyndns2 نفسها التي تراها مع المزودين الحقيقيين.
  • كل تحديث مقبول يعيد كتابة الملف /hosts/lab.hosts، وdnsmasq يراقب هذا المجلد ويقرأ الملف الجديد تلقائياً، فنرى الأثر في DNS مباشرة.

وإعداد ddns-updater في data/config.json يستخدم المزود المخصص custom، وهو نفسه الذي تستخدمه مع أي مزود يدعم dyndns2 وليس في قائمة الأداة:

{
  "settings": [
    {
      "provider": "custom",
      "domain": "home.example.com",
      "url": "https://api.ddns.test/nic/update?hostname=home.example.com&token=lab-token-123",
      "ipv4key": "myip",
      "ipv6key": "myipv6",
      "success_regex": "^(good|nochg)",
      "ip_version": "ipv4"
    }
  ]
}

وملف compose.yaml سوف يكون كما يلي:

name: lab-ddns

networks:
  lab:
    enable_ipv6: true
    ipam:
      config:
        - subnet: 10.74.74.0/24
        - subnet: fd00:74:74::/64

volumes:
  hosts:
  state:

services:
  mock:
    image: python:3.13-alpine
    container_name: lab-ddns-mock
    command: ["python", "-u", "/app/mock.py"]
    environment:
      - LAB_TOKEN=lab-token-123
    volumes:
      - ./mock.py:/app/mock.py:ro
      - ./certs:/certs:ro
      - hosts:/hosts
      - state:/state
    networks:
      lab:
        aliases: [api.ddns.test, ip.ddns.test]

  dns:
    image: alpine:3.24
    container_name: lab-ddns-dns
    command: ["sh", "-c", "apk add --no-cache dnsmasq >/dev/null && exec dnsmasq -k --no-resolv --no-hosts --hostsdir=/hosts --local-ttl=60 --log-queries --log-facility=-"]
    volumes:
      - hosts:/hosts:ro
    networks:
      lab:
        ipv4_address: 10.74.74.53

  updater:
    image: qmcgaw/ddns-updater:v2.10.0
    container_name: lab-ddns-updater
    environment:
      - PERIOD=1m
      - UPDATE_COOLDOWN_PERIOD=30s
      - PUBLICIP_FETCHERS=http
      - PUBLICIPV4_HTTP_PROVIDERS=url:https://ip.ddns.test/ip
      - RESOLVER_ADDRESS=10.74.74.53:53
      - SSL_CERT_FILE=/certs/ca.crt
      - TZ=UTC
    volumes:
      - ./data:/updater/data
      - ./certs/ca.crt:/certs/ca.crt:ro
    ports:
      - 127.0.0.1:18741:8000
    networks:
      - lab

  client:
    image: alpine:3.24
    container_name: lab-ddns-client
    command: ["sh", "-c", "apk add --no-cache bash curl jq bind-tools ddclient ca-certificates >/dev/null && cp /certs/ca.crt /usr/local/share/ca-certificates/lab.crt && update-ca-certificates && echo ready; exec sleep infinity"]
    volumes:
      - ./certs/ca.crt:/certs/ca.crt:ro
      - ./work:/work
    networks:
      - lab

في الإعداد أعلاه لاحظ التالي:

  • PUBLICIPV4_HTTP_PROVIDERS=url:https://ip.ddns.test/ip يجعل الأداة تسأل خدمة الصدى الوهمية بدل الخدمات الحقيقية، وRESOLVER_ADDRESS يجعلها تسأل dnsmasq عن السجل.
  • SSL_CERT_FILE يجعل الأداة تثق بجهة الشهادات التي أنشأناها، ولأنه يستبدل شهادات الثقة كلها فسوف ترى تحذيراً عن github.com عند التشغيل، فالأداة تحاول أن تتحقق من وجود إصدار أحدث ولا تثق بشهادته داخل التجربة، وهذا التحذير لا يظهر في التشغيل العادي.
  • PERIOD=1m قصيرة حتى لا ننتظر خمس دقائق في كل خطوة، وفي الاستخدام الحقيقي اتركها خمس دقائق.
  • الواجهة منشورة على 127.0.0.1:18741 فقط.

التشغيل والتأكد من السجل الأول

docker compose up -d
docker exec lab-ddns-client dig +noall +answer @10.74.74.53 home.example.com A home.example.com AAAA

انتظر حتى تظهر كلمة ready في docker logs lab-ddns-client، لأن client وdns يثبتان أدواتهما عند أول تشغيل، ثم سوف يكون المخرج كما يلي، فالسجل يبدأ بالعنوان 203.0.113.10، وخدمة الصدى تعيد العنوان نفسه، فلا يوجد ما يحدث:

home.example.com.	60	IN	A	203.0.113.10
home.example.com.	60	IN	AAAA	2001:db8:10::10

وإذا انتظرت دورة واحدة ثم قرأت سجل المزود الوهمي بالأمر docker logs lab-ddns-mock فسوف تجد أن ddns-updater سأل عن عنوانه فقط ولم يرسل أي تحديث:

21:19:21 fake provider listening on :443
21:19:35 GET /ip -> 203.0.113.10

نحاكي تغيير العنوان

الآن سوف نقوم بدور مزود الخدمة ونعطي البيت عنواناً جديداً، ثم ننتظر دقيقة حتى تأتي الدورة التالية:

docker exec lab-ddns-mock sh -c 'echo 198.51.100.77 > /state/ip4'
sleep 65
docker logs --since 70s lab-ddns-updater
docker logs --since 70s lab-ddns-mock
docker exec lab-ddns-client dig +noall +answer @10.74.74.53 home.example.com A

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

2026-10-05T21:20:35Z INFO ipv4 address of home.example.com is 203.0.113.10 and your ipv4 address is 198.51.100.77
2026-10-05T21:20:35Z INFO Updating record [domain: example.com | owner: home | provider: custom | ip: ipv4] to use 198.51.100.77

21:20:35 GET /ip -> 198.51.100.77
21:20:35 GET /nic/update hostname=home.example.com myip=198.51.100.77 agent="DDNS-Updater [email protected]"
21:20:35   record home.example.com A: 203.0.113.10 -> 198.51.100.77
21:20:35   -> good 198.51.100.77

home.example.com.	60	IN	A	198.51.100.77

في المخرج أعلاه لاحظ التالي:

  • السطر الأول هو المقارنة: الأداة سألت DNS فوجدت 203.0.113.10، وسألت خدمة الصدى فوجدت 198.51.100.77، فقررت التحديث.
  • الأداة قسمت الاسم إلى النطاق example.com والمالك home، وهذا هو الحقل Owner الذي تراه في الواجهة.
  • المزود الوهمي استقبل طلب dyndns2 بالعنوان الجديد ورد good، والرد يطابق التعبير ^(good|nochg) الذي وضعناه في success_regex، فاعتبرته الأداة نجاحاً.
  • بعد ثانية واحدة صار DNS يعيد العنوان الجديد، وفي الواقع سوف يأخذ الزوار العنوان الجديد خلال مدة بقاء السجل، أي 60 ثانية هنا.

وفي الدورات التالية لن تجد في سجل المزود إلا GET /ip، لأن العنوان لم يتغير. والآن افتح http://127.0.0.1:18741 في المتصفح، وسوف تجد حالة كل سجل والعناوين السابقة، وفي الصورة التالية أضفنا سجلاً ثانياً nas.example.com بتوكن خاطئ لنرى الحالتين معاً:

واجهة ddns-updater الويب فيها سجلان: home.example.com بحالة Up to date وعنوانه الحالي 203.0.113.55 والعناوين السابقة 198.51.100.77 و203.0.113.10، وnas.example.com بحالة Failure مع الرسالة HTTP status is not valid: 401: badauth
واجهة ddns-updater: سجل محدث وسجل فشل بسبب التوكن

نكسر التوكن ونقرأ الخطأ

بعد أن عمل كل شيء، سوف نغير التوكن في الإعداد إلى قيمة خاطئة، ثم نغير العنوان مرة أخرى حتى تحاول الأداة التحديث:

sed -i 's/token=lab-token-123/token=wrong-token/' data/config.json
docker restart lab-ddns-updater
docker exec lab-ddns-mock sh -c 'echo 203.0.113.55 > /state/ip4'
sleep 65
docker logs --since 70s lab-ddns-updater

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

2026-10-05T21:22:02Z INFO ipv4 address of home.example.com is 198.51.100.77 and your ipv4 address is 203.0.113.55
2026-10-05T21:22:02Z INFO Updating record [domain: example.com | owner: home | provider: custom | ip: ipv4] to use 203.0.113.55
2026-10-05T21:22:02Z ERROR HTTP status is not valid: 401: badauth

والرسالة واضحة، فالمزود رفض الطلب بالرمز 401 والرد badauth، والسجل بقي على العنوان القديم، وهذه بالضبط الحالة التي يتعطل فيها موقعك دون أن تعرف إذا لم تراقب السجل، كما سيأتي في قسم المراقبة. والأداة سوف تعيد المحاولة في كل دورة، فإذا أعدت التوكن الصحيح وأعدت تشغيلها يتحدث السجل مباشرة:

sed -i 's/token=wrong-token/token=lab-token-123/' data/config.json
docker restart lab-ddns-updater
sleep 10
docker logs --since 15s lab-ddns-mock
21:22:37 GET /ip -> 203.0.113.55
21:22:37 GET /nic/update hostname=home.example.com myip=203.0.113.55 agent="DDNS-Updater [email protected]"
21:22:37   record home.example.com A: 198.51.100.77 -> 203.0.113.55
21:22:37   -> good 203.0.113.55

نختبر سكربت Cloudflare على الواجهة الوهمية

الآن نوقف ddns-updater حتى لا يتنافس محدثان على السجل نفسه، ونضع السكربت cf-ddns.sh من قسم السكربت في المجلد work، ومعه ملف أسرار يشير إلى الواجهة الوهمية بدل Cloudflare:

docker stop lab-ddns-updater
cat > work/cf-ddns.env <<'EOF'
CF_API_TOKEN=lab-token-123
CF_ZONE_ID=0123456789abcdef0123456789abcdef
CF_RECORD_NAME=home.example.com
CF_API=https://api.ddns.test/client/v4
EOF
docker exec lab-ddns-client bash -c 'install -m 755 /work/cf-ddns.sh /usr/local/bin/cf-ddns.sh; install -m 600 /work/cf-ddns.env /etc/cf-ddns.env'
docker exec lab-ddns-mock sh -c 'echo 192.0.2.44 > /state/ip4; echo 2001:db8:20::77 > /state/ip6'
docker exec lab-ddns-client bash -c 'IP_URL=https://ip.ddns.test/ip cf-ddns.sh; echo "exit=$?"'
docker exec lab-ddns-client bash -c 'IP_URL=https://ip.ddns.test/ip cf-ddns.sh; echo "exit=$?"'
docker exec lab-ddns-client bash -c 'CF_RECORD_TYPE=AAAA IP_URL=https://ip.ddns.test/ip6 cf-ddns.sh; echo "exit=$?"'
docker exec lab-ddns-client dig +noall +answer @10.74.74.53 home.example.com A home.example.com AAAA

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

cf-ddns: updated A home.example.com to 192.0.2.44
exit=0
exit=0
cf-ddns: updated AAAA home.example.com to 2001:db8:20::77
exit=0
home.example.com.	60	IN	A	192.0.2.44
home.example.com.	60	IN	AAAA	2001:db8:20::77

وسجل المزود الوهمي يبين ما حدث في كل تشغيل:

21:24:57 GET /ip -> 192.0.2.44
21:24:57 GET /client/v4/zones/0123456789abcdef0123456789abcdef/dns_records?type=A&name=home.example.com -> 1 record(s)
21:24:57 PATCH /client/v4/zones/0123456789abcdef0123456789abcdef/dns_records/0e603bfc {"content": "192.0.2.44"}
21:24:57   record home.example.com A: 203.0.113.55 -> 192.0.2.44
21:24:57 GET /ip -> 192.0.2.44
21:24:58 GET /ip6 -> 2001:db8:20::77
21:24:58 GET /client/v4/zones/0123456789abcdef0123456789abcdef/dns_records?type=AAAA&name=home.example.com -> 1 record(s)
21:24:58 PATCH /client/v4/zones/0123456789abcdef0123456789abcdef/dns_records/ab7e357d {"content": "2001:db8:20::77"}
21:24:58   record home.example.com AAAA: 2001:db8:10::10 -> 2001:db8:20::77

في المخرج أعلاه لاحظ أن التشغيل الثاني لم يطبع شيئاً، وأن سجل المزود فيه للتشغيل الثاني سطر GET /ip واحد فقط، أي أن السكربت وجد العنوان في ملف الحالة فلم يتصل بالواجهة أصلاً. والمعرفات 0e603bfc وab7e357d من المزود الوهمي، أما في Cloudflare فهي سلاسل أطول، والسكربت لا يهتم بشكلها لأنه يقرؤها من الرد.

وإذا وضعت توكناً خاطئاً في الملف /etc/cf-ddns.env وغيرت العنوان، فسوف يتوقف السكربت عند أول طلب للواجهة:

curl: (22) The requested URL returned error: 403
exit=22

والرمز 403 مع الرسالة Authentication error في سجل المزود هو ما تعيده Cloudflare أيضاً للتوكن الذي لا يملك صلاحية على المنطقة، وبما أن السكربت توقف قبل أن يحفظ الحالة، فإنه سوف يحاول من جديد في الدورة التالية بعد أن تصلح التوكن.

نجرب ddclient على بروتوكول dyndns2

والإعداد التالي يجعل ddclient يكتشف العنوان من خدمة الصدى ويرسل التحديث بأسلوب dyndns2، وهو الشكل نفسه الذي تستخدمه مع Dynu أو No-IP أو أي مزود متوافق مع تغيير server والاسم والتوكن. احفظه في work/ddclient.conf:

# /etc/ddclient/ddclient.conf
daemon=300
syslog=no
ssl=yes
usev4=webv4
webv4=https://ip.ddns.test/ip
protocol=dyndns2
server=api.ddns.test
login=home
password='lab-token-123'
home.example.com
docker exec lab-ddns-mock sh -c 'echo 203.0.113.80 > /state/ip4'
docker exec lab-ddns-client sh -c 'install -m 600 /work/ddclient.conf /etc/ddclient/ddclient.conf; ddclient -foreground -daemon=0 -verbose -noquiet'

والمخرج سوف يكون كما يلي، ومعه سطر المزود الوهمي:

INFO:    [dyndns2][home.example.com]> setting IPv4 address to 203.0.113.80
SUCCESS: [dyndns2][home.example.com]> IPv4 address set to 203.0.113.80

21:25:23 GET /nic/update hostname=home.example.com myip=203.0.113.80 agent="ddclient/4.0.0"
21:25:23   -> good 203.0.113.80

في الإعداد أعلاه لاحظ التالي:

  • usev4=webv4 وwebv4= يجعلان ddclient يسأل خدمة صدى عن العنوان، وفي الاستخدام الحقيقي ضع خدمة HTTPS موثوقة أو اترك القيمة الافتراضية.
  • -foreground -daemon=0 يشغل الأداة مرة واحدة ويطبع النتيجة، وهذا مفيد للتجربة. والسطر daemon=300 في الملف هو ما يجعلها تعمل كل خمس دقائق عندما تشغلها خدمة النظام. وإذا شغلتها دون -foreground فإنها تنتقل إلى الخلفية مباشرة ولا تطبع شيئاً، فإذا لم تجد أي مخرج فهذا هو السبب.
  • الملف يحمل التوكن، لذلك صلاحيته 600.

تنظيف التجربة

docker compose down -v
docker rmi qmcgaw/ddns-updater:v2.10.0 python:3.13-alpine alpine:3.24
cd ~ && rm -rf ~/ddns-lab

كيف تحمي التوكن والسجل؟

محدث DDNS يعمل في بيتك ويحمل مفتاحاً يغير مكان اسمك على الإنترنت، فمن يحصل على التوكن يستطيع أن يوجه زوارك إلى سيرفره، وهذا يكفي لصفحة تسجيل دخول مزيفة على اسمك. وفيما يلي ما يجب أن تقوم به:

  • أقل صلاحية ممكنة Least Privilege: توكن لكل محدث، مقيد بالمنطقة التي فيها الاسم، وبصلاحية تعديل DNS فقط كما شرحنا في Cloudflare. وإذا كان المزود لا يسمح بالتقييد مثل DuckDNS، فلا تضع في الحساب نفسه أسماء لا علاقة لها بهذا المحدث.
  • لا تضع التوكن في Git أبداً: ضعه في ملف .env أو ملف إعداد بصلاحية 600، وأضف الملف إلى .gitignore، والسبب أن المستودع ينسخ ويرفع وتراه أطراف كثيرة، وفحص الأسرار في GitHub يكتشف بعض أنواع التوكن بعد أن تكون قد رفعتها. وإذا تسرب توكن فألغه وأنشئ غيره مباشرة، ولا تكتف بحذف الملف في Commit جديد، والسبب أن تاريخ المستودع ما زال يحمله.
  • لا تضع التوكن في الرابط إن كان هناك بديل: الروابط تحفظ في سجلات السيرفرات وسجل الأوامر Shell History، ولذلك يفضل deSEC ترويسة Authorization كما رأينا.
  • مصدر العنوان عبر HTTPS: اكتشاف العنوان عبر HTTP أو DNS غير محمي يسمح لمن في الطريق أن يرد بعنوان من اختياره، فتسجل أنت عنوانه، لذلك استخدم خدمات صدى عبر HTTPS، وتحقق من شكل الرد قبل إرساله.
  • لا تنشر واجهة المحدث: واجهة ddns-updater بلا تسجيل دخول، فاربطها ب 127.0.0.1 وافتحها عبر نفق SSH أو شبكة WireGuard، ولا تضعها خلف Reverse Proxy عام.
  • اعرف ما يكشفه الاسم: أي شخص يعرف اسمك يستطيع أن يعرف عنوان بيتك بأمر dig واحد، ولذلك لا تفتح في ال Router إلا المنفذ الذي تحتاجه، وأمن السيرفر نفسه كما في تأمين خادم VPS من أول دخول، فالخطوات نفسها تنطبق على سيرفر البيت.

راقب أن السجل يطابق عنوانك

رأينا في التجربة أن المحدث قد يفشل بسبب توكن منتهي أو حساب معلق أو اسم لم تؤكده في No-IP، ويستمر في المحاولة دون أن يخبرك أحد، لذلك أضف فحصاً مستقلاً يقارن عنوانك العام بما يعيده DNS، والسكربت التالي يقوم بذلك ويعيد رمز خروج غير الصفر عند الاختلاف، فتستطيع أن تربطه بأي نظام تنبيه:

#!/usr/bin/env bash
# Compares your public IPv4 address with the A record that DNS returns for the name.
set -euo pipefail
NAME=${1:-home.example.com}
NS=${2:-1.1.1.1}
PUBLIC=$(curl -4 -fsS --max-time 10 "${IP_URL:-https://api.ipify.org}")
DNS=$(dig +short "$NAME" A @"$NS" | tail -n1)
if [[ $PUBLIC == "$DNS" ]]; then
  echo "OK $NAME = $PUBLIC"
else
  echo "MISMATCH $NAME: dns=${DNS:-none} public=$PUBLIC" >&2
  exit 1
fi

وإذا أردت أن تجربه في التجربة العملية فاحفظه في ~/ddns-lab/work/ddns-check.sh، ثم شغله من client بعد أن تغير العنوان وقبل أن يعمل المحدث، ثم مرة أخرى بعد التحديث:

docker exec lab-ddns-client bash -c 'install -m 755 /work/ddns-check.sh /usr/local/bin/ddns-check.sh'
docker exec lab-ddns-client bash -c 'IP_URL=https://ip.ddns.test/ip ddns-check.sh home.example.com 10.74.74.53; echo "exit=$?"'

والمخرج قبل التحديث سوف يكون كما يلي:

MISMATCH home.example.com: dns=192.0.2.44 public=198.51.100.77
exit=1

وبعد التحديث:

OK home.example.com = 198.51.100.77
exit=0

ولاحظ أن السكربت يسأل Resolver معيناً، والأدق أن تسأل سيرفر DNS الرسمي لنطاقك مباشرة، مثل ddns-check.sh home.example.com ns1.example-dns.net، والسبب أن ال Resolver العام قد يعيد جواباً محفوظاً من قبل التحديث خلال مدة TTL، وقد شرحنا كيف تعرف سيرفرات نطاقك الرسمية في قسم أدوات الفحص. ولا تشغل هذا الفحص كل دقيقة، فكل عشر دقائق أو ربع ساعة يكفي، وأداة ddns-updater فيها فحص مشابه في ال Healthcheck الخاص بها.

مشكلات شائعة وكيف تشخصها

السجل محدث ولا يصل أحد إلى الخدمة

إذا كان dig يعيد عنوانك الصحيح والموقع لا يفتح من الخارج، فالمشكلة ليست في DDNS. ابدأ بمقارنة عنوان WAN في ال Router بالعنوان العام لتعرف إن كنت خلف CGNAT، ثم تأكد من توجيه المنفذ ومن أن جدار الحماية على السيرفر يسمح به، ثم جرب من شبكة الهاتف وليس من شبكة البيت، والسبب أن كثيراً من ال Routers لا تدعم Hairpin NAT فيفشل الاتصال من الداخل وحده، وقد شرحنا هذه الحالات في وجهت المنفذ ولا يصل أحد ويعمل من الخارج ولا يعمل من داخل المنزل.

بعض الزوار ما زالوا على العنوان القديم

اسأل سيرفر DNS الرسمي مباشرة بالأمر dig @ns1.your-dns-provider home.example.com A، فإذا كان الجواب صحيحاً فالسجل محدث والمشكلة في التخزين المؤقت عند ال Resolvers أو في جهاز الزائر، وسوف تنتهي خلال مدة TTL القديمة. وإذا كانت المدة طويلة قبل أن تخفضها، فإن ال Resolvers التي حفظت السجل قبل التخفيض تبقى على المدة القديمة، لذلك اجعل المدة قصيرة من أول يوم وليس بعد أول مشكلة.

يعمل على IPv4 ولا يعمل على IPv6

قارن dig AAAA بالعنوان الثابت للسيرفر في ip -6 addr، فإذا كان السجل يحمل عنواناً مؤقتاً Temporary أو عنوان ال Router فأصلح مصدر العنوان كما في قسم IPv6، وتأكد أن جدار الحماية في ال Router يسمح بالمنفذ لهذا الجهاز. وإذا كانت أداتك تعمل داخل Container على الشبكة الافتراضية فلن ترى IPv6 أصلاً، وهذا سبب network_mode: host في إعداد cloudflare-ddns. وإذا لم تكن تحتاج IPv6 فاحذف سجل AAAA بدل أن تتركه قديماً.

المزود يرفض الطلبات أو يحظرك

إذا ظهرت رموز مثل 429 أو ردود مثل abuse، فالمحدث يرسل التحديث في كل دورة حتى لو لم يتغير العنوان، أو أن محدثين يعملان معاً على الاسم نفسه، مثل ال Router و Container في الوقت نفسه، فيتبادلان التحديث. أوقف كل المحدثات إلا واحداً، وتأكد أنه يقارن قبل أن يرسل، فحد deSEC مثلاً طلبان كل دقيقتين لكل نطاق.

badauth أو 401 أو 403

التوكن خاطئ أو انتهت صلاحيته أو ليست له صلاحية على هذا الاسم. في Cloudflare تأكد أن المنطقة في Zone Resources هي المنطقة التي فيها الاسم، وفي deSEC تأكد أنك تستخدم التوكن وليس كلمة مرور الحساب، وفي No-IP تأكد أن الاسم لم يدخل فترة Redemption بسبب عدم التأكيد.

تدريب عملي

  1. في التجربة العملية أضف إلى data/config.json سجلاً ثانياً بنوع "ip_version": "ipv6" واجعل PUBLICIPV6_HTTP_PROVIDERS=url:https://ip.ddns.test/ip6، ثم غير الملف /state/ip6 وتأكد أن سجل AAAA تغير.
  2. شغل سكربت المراقبة ddns-check.sh داخل client كل دقيقة بحلقة while، ثم أوقف المحدث وغير العنوان، وقس كم دقيقة مرت قبل أن ينبهك.

الخلاصة

  • DDNS يبقي اسماً ثابتاً على عنوان يتغير، عن طريق محدث يكتشف عنوانك العام ويعدل السجل عبر واجهة المزود عند التغيير فقط، مع مدة بقاء قصيرة.
  • DDNS لا يفيد خلف CGNAT أو عندما يمنع المزود المنافذ، فاختبر عنوان WAN قبل أن تبدأ، والحل في هذه الحالة نفق أو عنوان عام من المزود.
  • الاسم المجاني عند DuckDNS أو deSEC بداية جيدة، ولكن الخدمة المهمة تستحق نطاقك الخاص، والتوكن فيه مقيد بمنطقة واحدة وصلاحية DNS Edit.
  • ddns-updater هو الخيار الأشمل في Container، وcloudflare-ddns من favonia لنطاقات Cloudflare بدل أداة oznu المؤرشفة، وddclient موجود في كل توزيعة، وسكربت curl يكفي لسجل واحد إذا كان يتحقق ويقارن.
  • في IPv6 حدث سجل AAAA بعنوان السيرفر الثابت وليس المؤقت، أو احذفه إن لم تكن تحدثه.
  • احم التوكن، ولا تنشر واجهة المحدث، وراقب أن السجل يطابق عنوانك، لأن المحدث المعطل لا ينبهك وحده.
دليلتشغيل خادمك من إنترنت المنزل: كل طرق الوصول من الخارجكيف تصل إلى سيرفر في بيتك من الإنترنت حتى لو كنت خلف CGNAT، وما الطريقة المناسبة لك من بين ثماني طرق.دليلشرح DNS وسجلاته للمبتدئين بمثال حقيقيكيف يتحول اسم نطاقك إلى عنوان سيرفرك، وما وظيفة كل سجل تضيفه في لوحة النطاق، والأخطاء التي تضيع فيها الساعات عادة.دليلما هو NAT؟ شرح ترجمة العناوين وتوجيه المنافذلماذا تخرج كل أجهزة بيتك بعنوان عام واحد، وكيف يترجم ال Router العناوين، ولماذا يفشل توجيه المنافذ خلف CGNAT.دليلكيف يصل الإنترنت إلى بيتك؟ من ال Router إلى الكابلات البحريةنتتبع طلباً واحداً من حاسوبك إلى سيرفر في بلد آخر ثم عودته، فتعرف من يحمله في كل محطة ولماذا يتأخر الرد.دليلخدمات Cloudflare بكلمات بسيطة وبدائلها مفتوحة المصدرما الذي تقدمه كل خدمة من Cloudflare، وما المتاح منها مجاناً، وما البديل مفتوح المصدر الذي تشغله على سيرفرك.

سجل التحديثات

أكتوبر 2026: كتابة الدليل واختباره على ddns-updater 2.10.0 وddclient 4.0.0 مع مزود وهمي محلي.

نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.