SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor · آخر تحديث في 2026-09-05

حل أخطاء 429 وحدود المعدل في SearXNG

يحدث الخطأ 429 في SearXNG بسبب محدِّدك المحلي أو حظر محركات البحث لعنوان خادمك. اقرأ السجل لتمييز الحالتين وإصلاح السبب الصحيح.

لماذا يعرض SearXNG أخطاء 429

يعرض مثيل SearXNG المستضاف ذاتياً أخطاء 429 لسببين منفصلين، وعادةً لا يكون حد المعدل الذي تحتاج إلى إصلاحه هو الحد الذي تتوقعه. السبب الأول محلي: قرر محدِّد المعدل الخاص بـSearXNG أن الطلب صدر عن bot، فأجاب بـToo Many Requests مع الحالة 429. السبب الثاني من جهة الخدمة upstream: رفض محرك بحث عنوان IP الخاص بخادمك، ويظهر ذلك للمستخدمين على شكل صفحة نتائج تنقصها بعض العناصر، وليس على شكل خطأ 429.

لا يوجد إصلاح مشترك للحالتين. محدِّد المعدل تابع لك، لذلك يمكنك تغييره. أما الحظر من جهة الخدمة upstream فيحدث على جانب Google، لذلك لن يزيله أي شيء في settings.yml. يوضح السجل أي حالة تواجهها خلال نحو دقيقة، فابدأ من هناك.

يفترض هذا الدليل أنك تستخدم تثبيت الحاوية الموضح في مثيل SearXNG مستضاف ذاتياً على VPS الخاص بك. تأتي كل أسماء الإعدادات الواردة أدناه من وثائق الخدمة upstream وشيفرتها المصدرية الحالية، وقد جرى التحقق منها في August 2026.

اقرأ السجل قبل تغيير أي إعداد

أعد إنتاج المشكلة مع إبقاء نافذة السجل مفتوحة.

cd ./searxng/
docker compose logs -f searxng-core

تأتي رسائل المحدِّد من المسجِّل المسمى searx.limiter، وتتضمن عنوان IP. يظهر التطابق مع قائمة الحظر بالشكل BLOCK 203.0.113.10: matched BLOCKLIST، بينما يظهر التطابق مع قائمة السماح بالشكل PASS 203.0.113.10: matched PASSLIST. إذا تعذر على المحدِّد الوصول إلى مخزن العدادات، فسيظهر في السجل The limiter requires Valkey, please consult the documentation، وهذا يعني أنه لا يجري احتساب أي شيء على الإطلاق.

يُسجَّل كل فحص منفرد للروبوت بمستوى debug، لذلك لن تراه افتراضياً. فعّل debug لاختبار واحد في settings.yml:

general:
  debug: true

يضيف السجل بعد ذلك أسطراً بالصيغة NOT OK (http_accept_language) بجانب شبكة العميل، مع ذكر الفحص الذي فشل. عطّل ذلك مجدداً بعد الانتهاء، لأن الجهة upstream توصي بعدم تشغيل نسخة منشورة مع تفعيل debug.

تبدو أعطال المحركات مختلفة تماماً. فهي تذكر محركاً بدلاً من عنوان IP، وأكثرها شيوعاً انتهاء المهلة:

HTTP requests timeout (search duration : 3.1 s, timeout: 3.0 s)

توجد صفحة لهذا الأمر أيضاً. عند إبقاء enable_metrics على قيمته الافتراضية true، تسجّل نسختك أخطاء المحركات عند /stats/errors، وتعرض /preferences المحركات التي تستجيب حالياً. إذا كان /stats/errors ممتلئاً ولم يتضمن السجل أي أسطر searx.limiter، فالمحدِّد ليس سبب المشكلة.

ثبّت الإصدار قبل أن تبدأ أي تصحيح للأعطال

يتكوّن إعداد الحاوية من upstream من ملفين.

mkdir -p ./searxng/core-config/
cd ./searxng/

curl -fsSL \
    -O https://raw.githubusercontent.com/searxng/searxng/master/container/docker-compose.yml \
    -O https://raw.githubusercontent.com/searxng/searxng/master/container/.env.example

cp -i .env.example .env

يسحب ملف Compose القيمة docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}. ويعني المتغير غير المعيّن القيمة latest، بينما يعني latest أن المثيل سيتغير دون تدخّل منك عند تنفيذ docker compose pull التالية. لذلك قد يتوقف إعداد عمل الأسبوع الماضي عن التطابق مع الشيفرة التي تقرؤه. تحمل علامات SearXNG تاريخاً وcommit. وعلامة المثال في upstream .env.example اعتباراً من August 2026 هي 2026.3.25-541c6c3cb، لذا عيّن قيمة فعلية في .env:

SEARXNG_VERSION=2026.3.25-541c6c3cb

تحقق من العلامات المنشورة وثبّت الإصدار الذي اختبرته فعلياً، ثم صحّح الأعطال مقابل هدف ثابت. ويحتوي ملف .env نفسه على مفتاحك السري، لذا اقرأ كيفية عمل ملفات البيئة والأسرار في Docker Compose قبل أن تضع هذا الدليل في أي مستودع.

يحتاج محدِّد المعدل إلى Valkey، وإلا فلن يعمل

يحسب محدِّد المعدل الطلبات لكل عميل، ويجب مشاركة هذه العدادات بين عمليات العاملين. مخزن البيانات هذا هو Valkey، وهو fork مُصان من Redis. تستخدم الأدلة الأقدم لـSearXNG هذا الإعداد باسم redis:. أما الإصدارات الحالية فتقرأ valkey:، لذلك انسخ اسم المفتاح من التوثيق الحالي بدلاً من نسخه من منشور قديم. تعود بعض هذه الصفحات إلى فترة أقدم من ذلك، وتصف Searx بدلاً من SearXNG. وهما قاعدتا شيفرة مختلفتان، ولكل منهما محدِّد معدل مختلف. لذلك تحقّق من المشروع الذي كُتبت الصفحة له قبل نسخ كتلة إعداد منها.

use_default_settings: true
server:
  secret_key: "change-this-value"
  limiter: true
  public_instance: false
valkey:
  url: valkey://searxng-valkey:6379/0

يشغّل ملف compose العلوي خدمة searxng-valkey باستخدام الصورة docker.io/valkey/valkey:9-alpine، لذلك يُحل اسم المضيف هذا داخل شبكة compose. ويمكن ضبط القيمة نفسها باستخدام متغير البيئة SEARXNG_VALKEY_URL. كما يعمل عنوان Unix socket ‏unix:///path/to/socket.sock?db=0 عندما يشترك SearXNG وValkey في المضيف نفسه.

يعتمد ما يحدث عند غياب المخزن على مفتاح آخر. عند استخدام public_instance: false، يسجّل محدِّد المعدل خطأ Valkey ثم يتوقف، ولذلك يستمر المثيل في تقديم الخدمة من دون تحديد للمعدل على الإطلاق. عند استخدام public_instance: true، تستدعي العملية sys.exit(1) بدلاً من ذلك، لأن المثيل المفتوح مع تعطّل حماية الروبوتات يجمع اختبارات CAPTCHA (اختبار تورنغ العام المؤتمت بالكامل للتمييز بين أجهزة الكمبيوتر والبشر) من كل محرك خلال يوم واحد. إذا أعاد container التشغيل في حلقة مباشرة بعد ضبط public_instance: true، فهذه هي الحالة المقصودة، وسيذكر السطر الأخير قبل كل خروج Valkey.

ما الذي يحسبه محدِّد المعدل فعلياً

ChartSearXNG limiter: requests allowed per client IP, defaults in ip_limit.py
The data behind this chart
[
  {
    "label": "Burst, normal client",
    "max_requests": 15,
    "window": "20 seconds"
  },
  {
    "label": "Burst, flagged client",
    "max_requests": 2,
    "window": "20 seconds"
  },
  {
    "label": "Sustained, normal client",
    "max_requests": 150,
    "window": "10 minutes"
  },
  {
    "label": "Sustained, flagged client",
    "max_requests": 10,
    "window": "10 minutes"
  },
  {
    "label": "Any non-HTML format",
    "max_requests": 4,
    "window": "1 hour"
  },
  {
    "label": "Flagged requests before block",
    "max_requests": 3,
    "window": "30 days"
  }
]

يحصل العميل العادي على 15 طلباً ضمن نافذة اندفاع مدتها 20 ثانية، وعلى 150 طلباً ضمن نافذة مدتها 10 دقائق. بعد وضع علامة الاشتباه على طلب، ينخفض الحد للعميل نفسه إلى 2 طلباً لكل نافذة اندفاع. أما الصف الأخير فهو الأشد: بعد 3 طلبات موضوعة عليها علامة الاشتباه ضمن نافذة مدتها 30 يوماً، يُعاد توجيه ذلك العنوان إلى صفحة البداية بدلاً من تنفيذ البحث، ويذكر السجل BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /).

هذه الأرقام ثوابت في searx/botdetection/ip_limit.py. وليست إعدادات، ولا يعرضها limiter.toml، لذلك يتطلب تغييرها تعديل المصدر. أما ما يتحكم فيه /etc/searxng/limiter.toml فعلياً فهو بادئات العناوين المستخدمة لتجميع العملاء، وقائمة الوكلاء الوسيطة الموثوقة، والتحقق الاختياري من رمز الرابط، وقائمتا السماح والحظر.

يُوضَع على الطلب علامة الاشتباه استناداً إلى فحوص الرؤوس، ولكل فحص اسم ستراه في سجل التصحيح:

  • http_accept: لا يحتوي الرأس Accept على text/html.
  • http_accept_encoding: لا يذكر الرأس Accept-Encoding gzip ولا deflate.
  • http_accept_language: لا يوجد رأس Accept-Language.
  • http_connection: قيمة الرأس Connection مضبوطة على close.
  • http_user_agent: قيمة User-Agent مفقودة أو تطابق نمط روبوت معروفاً.
  • http_sec_fetch: قيمة الرأس Sec-Fetch-Mode أو Sec-Fetch-Dest ليست مما يرسله المتصفح.

يرسل المتصفح كل هذه العناصر. أما استدعاء curl عادي فيرسل عدداً قليلاً جداً منها، لذلك يُوضَع على طلب الاختبار المكتوب يدوياً علامة الاشتباه من المحاولة الأولى، بينما يعمل البحث نفسه في علامة تبويب المتصفح. لذلك فإن ظهور الرسالة «يعمل في المتصفح، لكن يحصل البرنامج النصي على 429» هو النتيجة المعتادة، وليس أمراً غامضاً.

خلف Reverse Proxy، يطبّق محدِّد المعدل الحظر على الجميع دفعة واحدة

هذه أكثر طريقة شائعة لتعطيل نسخة تعمل بشكل صحيح. يستخرج SearXNG عنوان العميل من أول IP غير موثوق به في X-Forwarded-For، ثم يعود إلى X-Real-IP، ثم يعود مرة أخرى إلى العنوان الذي فتح الاتصال. ويحدّد trusted_proxies في limiter.toml ما إذا كان سيقبل هذه الرؤوس أصلاً.

إذا لم يكن عنوان الـproxy مدرجاً في تلك القائمة، فسيتم تجاهل الرؤوس، وسيصل كل زائر بعنوان الـproxy. عندها يشتركون في عدّاد واحد، ولذلك يُحظر الموقع بالكامل بمجرد أن يتجاوز العدد الإجمالي 150 طلباً خلال 10 دقائق. ويكفي أن يعيد مستخدم واحد تحميل صفحة النتائج بضع مرات لتعطيل الموقع للجميع.

الثقة الزائدة أسوأ. إذا أدرجت نطاقاً عاماً، فبإمكان أي زائر إرسال رأس X-Forwarded-For خاص به واختيار هوية جديدة لكل طلب، مما يعطّل محدِّد المعدل لأي شخص يعرف كيفية استغلال ذلك. أدرج فقط العنوان الذي يتصل منه الـproxy الخاص بك. في Docker يكون هذا العنوان عادةً شبكة bridge داخل 172.16.0.0/12، ويكون هذا السطر معطّلاً بالتعليق افتراضياً.

[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48

trusted_proxies = [
  '127.0.0.0/8',
  '::1',
  '172.16.0.0/12',
]

يجب أن يرسل الـproxy الرؤوس أيضاً. لا يضيف Nginx أياً منها تلقائياً:

location / {
    proxy_pass http://127.0.0.1:8080;

    proxy_set_header Host              $host;
    proxy_set_header Connection        $http_connection;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
}

يضبط Caddy وTraefik الرؤوس المُعاد توجيهها تلقائياً، لذلك تحتاج معهما إلى تنفيذ الجزء trusted_proxies فقط من الإعداد. تتناول مقارنة Reverse Proxy لخدمة مستضافة ذاتياً المفاضلات بين الخيارات. للتحقق من أي من الإعدادين، فعّل debug، ثم أجرِ بحثاً واحداً من هاتفك عبر بيانات الهاتف المحمول، وتأكد من أن الشبكة في سطر السجل هي عنوان هاتفك وليست عنوان الـproxy.

يحصل وكيلك على أربعة طلبات API في الساعة

يكون إخراج JSON معطّلاً افتراضياً، لذلك يجب إضافة هذا الإخراج إلى الوكيل:

search:
  formats:
    - html
    - json

اقرأ الآن صف المخطط مرة أخرى. يُحتسب كل طلب يطلب تنسيقاً غير HTML ضمن نافذته الخاصة: 4 طلباً لكل 1 hour، لكل عنوان. يستهلك وكيل البحث هذا الحد في مهمة واحدة، وتُرجع كل مكالمة بعد ذلك الرمز 429. لا يمكن رفع الحد، لأن القيمة موجودة في المصدر.

الحل الصحيح هو إبلاغ أداة تحديد المعدل بأن هذا العميل معروف. أضف عنوانه إلى قائمة السماح في limiter.toml:

[botdetection.ip_lists]
block_ip = []

pass_ip = [
  '10.8.0.0/24',
]

pass_searxng_org = true

تتقدم pass_ip على كل طريقة أخرى، لذلك يتجاوز العميل المدرج في قائمة السماح فحوصات الرؤوس أيضاً، وتعمل مكالمة curl المجردة. اجعل النطاق أصغر ما يمكن، وفضّل شبكة VPN الفرعية أو شبكة حاويات على أي نطاق يمكن توجيهه. والحل الصحيح الآخر هو إبقاء الوكيل خارج المسار العام تماماً: وجّهه إلى عنوان الحاوية على الشبكة الداخلية، حيث لا يرى الـproxy وأداة تحديد المعدل حركة الشبكة. يشرح منح وكيل AI مهارة البحث عبر SearXNG كيفية إعداد ذلك.

تجنب توجيه الوكيل إلى مثيل عام يشغّله شخص آخر. فهذه أسرع طريقة لحظر عنوان IP الخاص بالمتطوع من محركات البحث upstream، ولهذا يكون تنسيق JSON معطّلاً افتراضياً من الأساس.

عندما تحجبك محركات البحث بدلاً من ذلك

ChartHow long SearXNG suspends an engine, search.suspended_times defaults
The data behind this chart
[
  {
    "label": "SearxEngineTooManyRequests",
    "suspended_seconds": 3600,
    "roughly": "1 hour"
  },
  {
    "label": "SearxEngineAccessDenied",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "SearxEngineCaptcha",
    "suspended_seconds": 86400,
    "roughly": "1 day"
  },
  {
    "label": "recaptcha_SearxEngineCaptcha",
    "suspended_seconds": 604800,
    "roughly": "7 days"
  },
  {
    "label": "cf_SearxEngineCaptcha",
    "suspended_seconds": 1296000,
    "roughly": "15 days"
  }
]

عندما يردّ محرك بإجابة 429 خاصة به أو بصفحة CAPTCHA، يرفع SearXNG استثناءً مسمّى ويتوقف عن إرسال الطلبات إلى ذلك المحرك لفترة. تعلّق إجابة تجاوز عدد الطلبات المحرك لمدة 3600 ثانية. وتعلّق إجابة CAPTCHA عادية أو إجابة رفض الوصول المحرك لمدة 1 day. أما CAPTCHA المقدَّمة عبر Cloudflare فتعلّق المحرك لمدة 15 days، وهي أطول مدة افتراضية في القائمة، لأن هذه الإجابة تعني أن الحظر مفروض عند الحافة، ولن تساعد إعادة المحاولة. يحدد صف CAPTCHA الذي ظهرت فيه من الصفوف الثلاثة ما يستحق التجربة تالياً، ولأخطاء CAPTCHA مجموعة إصلاحات خاصة بها بعد معرفة الاستثناء الذي سجّلته مثيلتك.

تستخدم حالات الفشل العادية إعدادات مختلفة. يعلّق انتهاء المهلة أو خطأ التحليل المحرك لمدة قصيرة مشتقة من search.ban_time_on_fail، الذي تكون قيمته الافتراضية 5 ثوانٍ، وتحدّه search.max_ban_time_on_fail عند 120 ثانية. لذلك يتعافى المحرك البطيء تلقائياً خلال بضع دقائق، بينما يظل المحرك المحجوب متوقفاً لساعات. يفسر هذا الفرق عرضاً يصفه المستخدمون بأنه عشوائي: تكون النتائج سليمة، ثم تختفي نتائج أحد المحركات طوال بقية فترة بعد الظهر.

يستحق انتهاء المهلة الإصلاح قبل إلقاء اللوم على أي جهة. القيمة الافتراضية لـ request_timeout هي 2.0 ثانية، وهي مدة قصيرة لخادم VPS صغير بعيد عن أقرب خادم حافة لأحد المحركات.

outgoing:
  request_timeout: 3.0
  max_request_timeout: 10.0
engines:
  - name: bing
    timeout: 5.0

يمثل request_timeout القيمة الافتراضية لكل محرك، ويمثل max_request_timeout الحد الأقصى، ويمكن لمحرك واحد ضبط قيمة timeout الخاصة به. تؤدي زيادة هذه القيم إلى مبادلة زمن تحميل الصفحة بعدد أقل من حالات الفشل، لذلك زدها بمقدار نصف ثانية في كل مرة وراقب /stats/errors بدلاً من القفز مباشرة إلى 10.

إذا كان أحد المحركات يحجب عنوانك فعلاً، فأزله. تنتظر كل عملية بحث أبطأ محرك، لذلك يؤدي الاحتفاظ بمحرك معلّق دائماً إلى زيادة زمن الاستجابة من دون إعادة أي نتائج.

use_default_settings:
  engines:
    remove:
      - google

طبّق التغييرات باستخدام docker compose restart searxng-core، ثم نفّذ بضع عمليات بحث وأعد تحميل /stats/errors. إذا بقيت الصفحة فارغة بعد خمس دقائق من الاستخدام الفعلي، فهذا يعني أن التغيير نجح.

سيُعامَل عنوان IP من مركز بيانات على أنه تابع لروبوت

ينتمي عنوان VPS الخاص بك إلى نطاق استضافة، وتقيّم محركات البحث الكبيرة هذه النطاقات على أنها مصادر آلية. وتعرض بعض هذه المحركات CAPTCHA لكل طلب صادر من مثل هذا العنوان، بغض النظر عن مدى ملاءمة الرؤوس أو بطء وتيرة الطلبات. لا يغيّر أي إعداد في settings.yml هذا التقييم. كما أن رؤية محركات البحث لخادمك بدلاً من الشخص الذي يكتب الاستعلام هي جوهر المقايضة المتعلقة بالخصوصية التي قبلت بها عند الاستضافة الذاتية. ويستحق ما الذي يخفيه SearXNG فعلياً القراءة قبل أن تفترض أنه يوفر حماية أوسع.

يمكنك تغيير المحركات التي تستعلم منها، وتحديد ما إذا كانت نسختك مدرجة للعامة. نادراً ما تتسبب نسخة خاصة يستخدمها أفراد منزل واحد في تشغيل هذه الآليات. أما النسخة العامة الموجودة على عنوان استضافة، فستتلقى عمليات تعليق من المحركات الأكثر تشدداً. وهذه هي الحالة المعتادة للبرنامج، وليست خطأً في إعداداتك. يستطيع SearXNG توجيه طلبات المحركات عبر proxy باستخدام outgoing.proxies أو outgoing.using_tor_proxy، ما ينقل حركة الشبكة إلى عنوان مختلف. وتُقيَّم عقد الخروج ومجموعات proxy الرخيصة بدرجة أسوأ من نطاقات الاستضافة، لذلك توقّع أن يؤدي هذا التغيير إلى تراجع النتائج.

راقب المثيل لتعرف بالمشكلة أولاً

يستجيب SearXNG على منفذه حتى عند تعليق جميع المحركات، لذلك يظل فحص التوافر الذي يراقب رمز الحالة فقط ناجحاً، بينما لا يعيد المثيل أي نتيجة. افحص المحتوى بدلاً من ذلك: أرسل بحثاً فعلياً وطابق كلمة تتوقع ظهورها في نص الاستجابة. ينفّذ مراقبة الكلمات المفتاحية عبر Uptime Kuma ذلك من دون أدوات إضافية. راقب /stats/errors بعد كل تحديث للإصدار أيضاً، لأن المحركات تغيّر HTML الخاص بها، وقد يتعطل المحلّل من دون أن تكون هناك أي مشكلة تتعلق بحدود المعدل.

FAQ

لماذا يعرض SearXNG الخطأ 429 لكل زائر بعد وضعه خلف reverse proxy؟

لأن أداة تحديد المعدل تحسب الـproxy على أنه العميل. لا يقرأ SearXNG X-Forwarded-For إلا عندما يكون عنوان الاتصال مدرجاً في trusted_proxies ضمن /etc/searxng/limiter.toml. إذا لم يكن العنوان مدرجاً، يشترك جميع الزوار في عدّاد واحد، ويتجاوزون معاً حد 150 طلباً خلال 10 دقائق. أضف العنوان الذي يتصل منه الـproxy، وهو في Docker عادةً نطاق bridge 172.16.0.0/12، وتأكد من أن الـproxy يرسل X-Real-IP وX-Forwarded-For. لا تُدرج أبداً نطاقاً لا تتحكم فيه، لأن الشبكة الموثوقة تتيح لأي زائر تعيين ذلك الترويسة واختيار هوية جديدة لكل طلب.

كم عدد طلبات API التي يسمح بها محدد معدل SearXNG في الساعة؟

أربعة طلبات لكل عنوان IP في الساعة. يُحتسب أي طلب يطلب تنسيقاً غير HTML ضمن نافذة منفصلة مدتها ساعة واحدة، ويُحدَّد هذا الحد في searx/botdetection/ip_limit.py بدلاً من limiter.toml، لذلك لا يمكن رفعه من الإعدادات. يتجاوز agent أو script هذا الحد في مهمة واحدة. أضف عنوان العميل إلى pass_ip في limiter.toml، أو صِل إلى المثيل عبر شبكة داخلية لا يرى فيها محدد المعدل الطلب.

لماذا تعود نتائج البحث فارغة من دون خطأ 429؟

ترفض محركات البحث خادمك، لا مستخدميك. افتح /stats/errors على المثيل الخاص بك. يذكر هذا الملف كل محرك فشل وسبب الفشل، وتعني خانة CAPTCHA أو access denied أن ذلك المحرك حظر عنوان IP الخاص بخادمك. يعلّق SearXNG المحرك بعد ذلك، لمدة ساعة عقب استجابة too many requests ولمدة يوم عقب CAPTCHA. لا يرفع أي إعداد محلي الحظر الصادر من الخدمة upstream، لذا أزل المحركات التي تحظر عنوانك وأبقِ المحركات التي تستجيب.

هل ينبغي تفعيل محدد المعدل على مثيل خاص؟

إذا لم يصل إلى المثيل أحد سواك، اترك limiter: false. فهو يضيف اعتماداً على Valkey ويحظر scripts الخاصة بك، كما أنه يحمي من traffic غير موجود لديك. فعّله فور حصول المثيل على عنوان عام، مع public_instance: true. هذا الاقتران مقصود: عند تفعيل public_instance: true من دون Valkey يعمل، تنتهي العملية بالحالة 1 بدلاً من التشغيل من دون حماية.