פתרון שגיאות 429 ומגבלות קצב ב-SearXNG
שגיאות 429 ב-SearXNG נובעות ממנגנון הגבלת הקצב המקומי או מחסימת IP מצד מנועי החיפוש. למדו לקרוא את הלוגים כדי להבדיל בין השניים וליישם את התיקון הנכון לשרת שלכם.
מדוע SearXNG מחזיר שגיאות 429
מופע SearXNG מאוחסן עצמית מחזיר שגיאות 429 משתי סיבות שאינן קשורות זו לזו, ומגבלת הקצב (rate limit) שעליכם לתקן היא בדרך כלל לא זו שאתם מניחים. הסיבה הראשונה היא מקומית: מנגנון הגבלת הקצב של SearXNG החליט שבקשה מסוימת הגיעה מבוט והשיב Too Many Requests עם סטטוס 429. הסיבה השנייה היא במעלה הזרם (upstream): מנוע חיפוש חסם את כתובת ה-IP של השרת שלכם, מה שמגיע למשתמשים שלכם כדף תוצאות עם חוסרים, ולא כשגיאת 429.
לשני המקרים אין פתרון משותף. מנגנון הגבלת הקצב הוא שלכם, לכן ניתן לשנות אותו. חסימה במעלה הזרם מתרחשת בצד של Google, לכן שום דבר ב-settings.yml שלכם לא יסיר אותה. הלוגים יגלו לכם בתוך דקה באיזה מקרה מדובר, לכן התחילו משם.
מדריך זה מניח התקנה במכולה (container) כפי שמתואר ב-מופע SearXNG מאוחסן עצמית על ה-VPS שלכם. כל שם הגדרה להלן מגיע מהתיעוד ומקוד המקור העדכניים של הפרויקט, כפי שנבדקו באוגוסט 2026.
קרא את הלוג לפני שינוי הגדרה
שחזרו את הבעיה כשהלוג פתוח לצפייה.
cd ./searxng/
docker compose logs -f searxng-coreהודעות ה-limiter מגיעות מהלוגר ששמו searx.limiter והן מציינות כתובת IP. פגיעה ב-blocklist מופיעה כ-BLOCK 203.0.113.10: matched BLOCKLIST, ופגיעה ב-allowlist מופיעה כ-PASS 203.0.113.10: matched PASSLIST. אם ה-limiter אינו מצליח להגיע למאגר המונים שלו, הלוג יציג The limiter requires Valkey, please consult the documentation, מה שאומר ששום דבר לא נספר כלל.
כל בדיקת בוט פרטנית מתועדת ברמת debug, לכן לא תראו אותה כברירת מחדל. הפעילו את ה-debug לצורך בדיקה אחת ב-settings.yml:
general:
debug: trueהלוג יוסיף לאחר מכן שורות במבנה NOT OK (http_accept_language) לצד רשת הלקוח, עם ציון הבדיקה שנכשלה. כבו את ה-debug לאחר מכן, כיוון שהנחיות ה-upstream אוסרות על הרצת מופע בסביבת ייצור עם debug פעיל.
כשלים במנוע (engine) נראים אחרת לגמרי. הם מציינים מנוע במקום כתובת IP, והנפוץ שבהם הוא timeout:
HTTP requests timeout (search duration : 3.1 s, timeout: 3.0 s)קיים גם דף ייעודי לכך. כאשר enable_metrics נשאר בערך ברירת המחדל שלו true, המופע שלכם מתעד שגיאות מנוע ב-/stats/errors, ו-/preferences מציג אילו מנועים משיבים כרגע. אם /stats/errors מלא והלוג אינו מכיל שורות searx.limiter, הבעיה אינה ב-limiter.
קבעו גרסה לפני שאתם מנפים שגיאות
הגדרת ה-container של ה-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 משמעותו שה-instance משתנה מתחת לידיכם ב-docker compose pull הבא, כך שהגדרה שעבדה בשבוע שעבר עלולה להפסיק להתאים לקוד שקורא אותה. התגיות של SearXNG נושאות תאריך ו-commit. תגית הדוגמה ב-upstream ב-.env.example נכון לאוגוסט 2026 היא 2026.3.25-541c6c3cb, לכן הגדירו אחת קבועה ב-.env:
SEARXNG_VERSION=2026.3.25-541c6c3cbבדקו את התגיות שפורסמו וקבעו (pin) את ה-release שבדקתם בפועל, ולאחר מכן בצעו ניפוי שגיאות מול יעד קבוע. אותו קובץ .env מכיל את ה-secret key שלכם, לכן קראו את איך קובצי env ו-secrets עובדים ב-Docker Compose לפני שאתם מבצעים commit לספרייה הזו לכל מקום שהוא.
המגביל דורש את Valkey, אחרת הוא לא יפעל
המנגנון סופר בקשות לפי לקוח, וספירות אלה חייבות להיות משותפות בין תהליכי worker. מאגר הנתונים המשמש לכך הוא Valkey, המזלג המתוחזק של Redis. במדריכים ישנים יותר של SearXNG ההגדרה הזו נקראת redis:. בגרסאות הנוכחיות נקרא המנגנון valkey:, לכן העתיקו את שם המפתח מהתיעוד העדכני ולא מפוסט ישן. חלק מהדפים האלה חוזרים אף יותר לאחור ומתארים את Searx במקום את SearXNG. מדובר בבסיס קוד אחר עם מנגנון limiter אחר. לכן בררו עבור איזה משני הפרויקטים נכתב הדף לפני שתעתיקו ממנו בלוק תצורה.
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 על גבי ה-image של docker.io/valkey/valkey:9-alpine, כך ששם המארח (host name) מתורגם בתוך רשת ה-compose. ניתן להגדיר את אותו הערך באמצעות משתנה הסביבה SEARXNG_VALKEY_URL, וכתובת URL של Unix socket (unix:///path/to/socket.sock?db=0) תעבוד כאשר SearXNG ו-Valkey חולקים את אותו מארח.
מה קורה כאשר מאגר הנתונים חסר תלוי במפתח נוסף. עם public_instance: false, המגביל מתעד את שגיאת Valkey ביומנים ומפסיק לפעול, כך שהמופע (instance) ממשיך לשרת בקשות ללא כל הגבלת קצב. עם public_instance: true, התהליך קורא ל-sys.exit(1) במקום זאת, כיוון שמופע פתוח ללא הגנה מפני בוטים יאסוף CAPTCHAs (מבחן טיורינג ציבורי אוטומטי להבחנה בין מחשבים לבני אדם) מכל מנוע חיפוש בתוך יום אחד. מכולה שמתחילה מחדש בלולאה מיד לאחר הגדרת public_instance: true סובלת מבעיה זו, והשורה האחרונה לפני כל יציאה מציינת את Valkey.
מה המגביל סופר בפועל
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 כן שולט בו הוא קידומות הכתובות המשמשות לקיבוץ לקוחות, רשימת ה-proxies המהימנים, בדיקת ה-token האופציונלית, ורשימות האישור והחסימה.
בקשה מסומנת כחשודה על ידי בדיקות headers, ולכל בדיקה יש שם שיופיע בלוג ה-debug:
http_accept: ה-header מסוגAcceptאינו מכיל אתtext/html.http_accept_encoding: ה-header מסוגAccept-Encodingאינו מציין אתgzipאו אתdeflate.http_accept_language: אין header מסוגAccept-Language.http_connection: ה-header מסוגConnectionמוגדר כ-close.http_user_agent: ה-User-Agentחסר או תואם דפוס ידוע של בוטים.http_sec_fetch: ה-header מסוג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, ושורה זו מגיעה כשהיא מוערת (commented out).
[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 מגדירים את כותרות ה-forwarded עבורכם, לכן איתם עליכם לבצע רק את החלק של trusted_proxies. היתרונות והחסרונות מפורטים ב-בחירת reverse proxy עבור שירות באירוח עצמי. כדי לוודא את תקינות ההגדרה, הפעילו את debug, בצעו חיפוש פעם אחת מהטלפון שלכם באמצעות רשת סלולרית, וודאו שהרשת בשורת הלוג היא הכתובת של הטלפון שלכם ולא הכתובת של ה-proxy.
הסוכן שלך מקבל ארבע בקשות API בשעה
פלט ה-JSON מושבת כברירת מחדל, לכן יש להוסיף אותו עבור הסוכן:
search:
formats:
- html
- jsonכעת קרא שוב את שורת הטבלה. כל בקשה המבקשת פורמט שאינו HTML נספרת בחלון זמן משלה: 4 בקשות לכל 1 hour, לכל כתובת. סוכן מחקר מנצל מכסה זו במשימה אחת, וכל קריאה לאחר מכן מחזירה 429. העלאת המגבלה אינה אפשרות, כיוון שהמספר מוטמע בקוד המקור.
הפתרון הנקי הוא להגדיר ל-limiter שהלקוח הזה אינו גורם זר. הוסף את הכתובת שלו לרשימת היתרים (pass list) ב-limiter.toml:
[botdetection.ip_lists]
block_ip = []
pass_ip = [
'10.8.0.0/24',
]
pass_searxng_org = trueל-pass_ip יש עדיפות על פני כל שיטה אחרת, לכן לקוח ברשימת היתרים מדלג גם על בדיקות ה-header, וקריאת curl פשוטה עובדת. שמור על הטווח מצומצם ככל האפשר, והעדף subnet של VPN או רשת מכולות (container network) על פני כל דבר שניתן לניתוב. הפתרון הנקי האחר הוא להרחיק את הסוכן מהנתיב הציבורי לחלוטין: כוון אותו לכתובת המכולה ברשת הפנימית, שם ה-proxy וה-limiter שלו לעולם לא יראו את התעבורה. חיבור זה מוסבר ב-מתן יכולת חיפוש SearXNG לסוכן AI.
האפשרות שיש להימנע ממנה היא הפניית סוכן למופע ציבורי שמישהו אחר מריץ. זו הדרך המהירה ביותר לגרום לחסימת כתובת ה-IP של מתנדב על ידי מנועים במעלה הזרם, וזו הסיבה לכך שפורמט ה-JSON מושבת כברירת מחדל מלכתחילה.
כאשר מנועי החיפוש חוסמים אתכם
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 מעלה חריגה (exception) מוגדרת ומפסיק לפנות לאותו מנוע למשך זמן מה. תשובת "too-many-requests" משעה את המנוע למשך 3600 שניות. דף CAPTCHA רגיל או תשובת "access-denied" משעים אותו למשך 1 day. דף CAPTCHA שמוגש דרך Cloudflare משעה את המנוע למשך 15 days, זמן ההשעיה המוגדר כברירת מחדל הארוך ביותר ברשימה, כיוון שתשובה כזו מעידה על חסימה ברמת ה-edge, וניסיונות חוזרים לא יועילו.
כשלים רגילים משתמשים בהגדרות שונות. פסק זמן (timeout) או שגיאת ניתוח (parse error) משעים את המנוע לזמן קצר הנגזר מ-search.ban_time_on_fail, שערכו כברירת מחדל הוא 5 שניות ומוגבל על ידי search.max_ban_time_on_fail ל-120 שניות. לכן, מנוע איטי מתאושש מעצמו בתוך דקות ספורות, בעוד שמנוע חסום מושבת למשך שעות. הבדל זה מסביר תופעה שמשתמשים מדווחים עליה כרנדומלית: התוצאות תקינות, ואז התוצאות ממנוע אחד נעלמות למשך שארית אחר הצהריים.
כדאי לטפל בפסקי זמן לפני שמטילים אשמה על גורם כלשהו. ערך ברירת המחדל של request_timeout הוא 2.0 שניות, זמן קצר מדי עבור שרת VPS קטן המרוחק משרת ה-edge הקרוב ביותר של המנוע.
outgoing:
request_timeout: 3.0
max_request_timeout: 10.0
engines:
- name: bing
timeout: 5.0request_timeout הוא ערך ברירת המחדל עבור כל מנוע, max_request_timeout הוא התקרה, ומנוע בודד יכול לשאת ערך timeout משלו. העלאת ערכים אלו באה על חשבון זמן התגובה של הדף בתמורה לפחות כשלים, לכן בצעו שינויים במרווחים של חצי שנייה ועקבו אחר /stats/errors במקום לקפוץ ישר ל-10.
עבור מנוע שחוסם בפועל את הכתובת שלכם, הסירו אותו. כל חיפוש ממתין למנוע האיטי ביותר שלו, לכן החזקת מנוע מושעה לצמיתות גורמת לעיכובים ואינה מניבה דבר.
use_default_settings:
engines:
remove:
- googleהחילו שינויים באמצעות docker compose restart searxng-core, לאחר מכן בצעו מספר חיפושים ורעננו את /stats/errors. דף ריק לאחר חמש דקות של שימוש אמיתי מעיד על כך שהשינוי עבד.
כתובת IP של מרכז נתונים תזוהה כבוט
כתובת ה-VPS שלכם שייכת לטווח כתובות של ספק אירוח, ומנועי החיפוש הגדולים מסווגים טווחים אלו כאוטומציה. חלקם מציגים CAPTCHA לכל בקשה שמגיעה מכתובת כזו, ללא קשר לנימוס ה-headers או לקצב הבקשות. שום הגדרה ב-settings.yml לא תשנה את הסיווג הזה. העובדה שהמנועים רואים את השרת שלכם במקום את האדם שמקליד את השאילתה היא חלק מהפשרה בפרטיות שביצעתם בעת אירוח עצמי, וכדאי לקרוא על כמה SearXNG באמת מסתיר לפני שתניחו שהוא מספק הגנה רחבה יותר.
מה שניתן לשנות הוא אילו מנועים יקבלו שאילתות והאם המופע שלכם רשום באופן ציבורי. מופע פרטי המשמש משק בית אחד כמעט לעולם לא יפעיל חסימות. מופע ציבורי שיושב על כתובת IP של ספק אירוח יצבור חסימות במנועים המחמירים ביותר; זהו המצב התקין של התוכנה ולא תקלה בתצורה שלכם. SearXNG יכול לנתב בקשות למנועים דרך proxy באמצעות outgoing.proxies או outgoing.using_tor_proxy, מה שמעביר את התעבורה לכתובת אחרת. צמתי יציאה (exit nodes) ומאגרי proxy זולים מדורגים גרוע יותר מטווחים של ספקי אירוח, לכן צפו לכך שמהלך כזה יפגע באיכות התוצאות.
ניטור המופע כדי לזהות תקלות בזמן אמת
SearXNG משיב בפורט שלו גם כאשר כל מנועי החיפוש מושבתים, לכן בדיקת זמינות (uptime check) שמסתמכת רק על קוד סטטוס תציג מצב תקין גם כשהמופע אינו מחזיר תוצאות. במקום זאת, יש לבדוק את תוכן התגובה: בצעו חיפוש אמיתי וחפשו מילה צפויה בגוף התגובה. ניטור מילות מפתח ב-Uptime Kuma מבצע בדיוק את זה ללא צורך בכלים נוספים. עקבו גם אחרי /stats/errors לאחר כל עדכון גרסה, כיוון שמנועי החיפוש משנים את מבנה ה-HTML שלהם, מה שעלול לגרום לכשל ב-parser ללא קשר למגבלות קצב (rate limit).
FAQ
מדוע SearXNG מחזיר שגיאת 429 לכל מבקר לאחר שהצבתי אותו מאחורי reverse proxy?
מכיוון שהמגביל (limiter) מחשיב את ה-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. לעולם אל תגדירו טווח שאינכם שולטים בו, כיוון שרשת מהימנה מאפשרת לכל מבקר לקבוע את ה-header הזה ולבחור זהות חדשה עבור כל בקשה.
כמה בקשות API לשעה מאפשר המגביל של SearXNG?
ארבע בקשות לכל כתובת IP בשעה. כל בקשה המבקשת פורמט שאינו HTML נספרת בחלון זמן נפרד של שעה, ומגבלה זו מוגדרת ב-searx/botdetection/ip_limit.py ולא ב-limiter.toml, לכן לא ניתן להעלות אותה דרך קובץ התצורה. סוכן (agent) או סקריפט מעבירים זאת במשימה אחת. הוסיפו את כתובת הלקוח ל-pass_ip בתוך limiter.toml, או גשו למופע (instance) דרך רשת פנימית שבה המגביל אינו רואה את הבקשה.
מדוע תוצאות החיפוש שלי חוזרות ריקות ללא שגיאת 429?
מנועי החיפוש מסרבים לשרת שלכם, לא למשתמשים שלכם. פתחו את /stats/errors במופע שלכם: הוא מפרט כל מנוע שנכשל והסיבה לכך. כניסה המציינת CAPTCHA או access-denied פירושה שהמנוע חסם את כתובת ה-IP של השרת שלכם. SearXNG משעה אז את המנוע למשך שעה לאחר תשובת "יותר מדי בקשות" ולמשך יום לאחר CAPTCHA. שום הגדרה מקומית לא תסיר חסימה מצד השרת המרוחק, לכן הסירו את המנועים שחוסמים את הכתובת שלכם והשאירו את אלו שמשיבים.
האם עליי להפעיל את המגביל במופע פרטי?
אם שום דבר לא מגיע למופע מלבדכם, השאירו את limiter: false. הוא מוסיף תלות ב-Valkey וחוסם את הסקריפטים שלכם, והוא מגן מפני תעבורה שאינכם חשופים אליה. הפעילו אותו ברגע שהמופע מקבל כתובת ציבורית, יחד עם public_instance: true. הצמד הזה מכוון: עם public_instance: true וללא Valkey תקין, התהליך יסתיים בסטטוס 1 במקום לרוץ ללא הגנה.