Cómo corregir límites y errores 429 en SearXNG
SearXNG devuelve 429 por el limitador local o por un bloqueo de la IP en los motores. Revise el registro y aplique la solución correcta sin adivinar.
Por qué SearXNG devuelve errores 429
Una instancia de SearXNG autohospedada devuelve errores 429 por dos motivos no relacionados, y el límite de tasa que debe corregir normalmente no es el que supone. El primer motivo es local: el limitador propio de SearXNG determina que una petición procede de un bot y responde Too Many Requests con el estado 429. El segundo es externo: un motor de búsqueda rechaza la dirección IP de su servidor, y sus usuarios lo reciben como una página de resultados a la que le faltan elementos, no como un error 429.
Los dos casos no comparten ninguna solución. El limitador es suyo, por lo que puede modificarlo. El bloqueo externo se produce en el lado de Google, por lo que nada en su settings.yml lo eliminará. Los registros indican cuál de los dos casos tiene en aproximadamente un minuto, así que empiece por ahí.
Esta guía presupone la instalación en contenedor descrita en una instancia de SearXNG autohospedada en su propio VPS. Todos los nombres de configuración que aparecen a continuación proceden de la documentación y el código fuente actuales del proyecto, comprobados en agosto de 2026.
Lea el registro antes de cambiar una configuración
Reproduzca el problema con una ventana del registro abierta.
cd ./searxng/
docker compose logs -f searxng-coreLos mensajes del limitador proceden del registrador llamado searx.limiter y contienen una dirección IP. Un acierto de la lista de bloqueo aparece como BLOCK 203.0.113.10: matched BLOCKLIST, mientras que un acierto de la lista de permitidos aparece como PASS 203.0.113.10: matched PASSLIST. Si el limitador no puede acceder a su almacén de contadores, el registro muestra The limiter requires Valkey, please consult the documentation. Esto significa que no se está contabilizando ninguna solicitud.
Cada comprobación individual de bots se registra con el nivel debug, por lo que no la verá de forma predeterminada. Active debug para una prueba en settings.yml:
general:
debug: trueEl registro añade entonces líneas con el formato NOT OK (http_accept_language) junto a la red del cliente e indica qué comprobación ha fallado. Desactive debug después, porque el proyecto original indica que no se debe ejecutar una instancia desplegada con debug activado.
Los fallos del motor tienen un aspecto completamente distinto. Indican un motor en lugar de una IP, y el caso más habitual es un tiempo de espera agotado:
HTTP requests timeout (search duration : 3.1 s, timeout: 3.0 s)También hay una página para esto. Si enable_metrics conserva su valor predeterminado de true, la instancia registra los errores del motor en /stats/errors, y /preferences muestra qué motores están respondiendo actualmente. Si /stats/errors está lleno y el registro no contiene líneas searx.limiter, el limitador no es el problema.
Fije la versión antes de depurar cualquier problema
La configuración del contenedor proporcionada por el proyecto upstream consta de dos archivos.
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 .envEl archivo de Compose obtiene docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}. Una variable sin definir significa latest, y latest significa que la instancia cambia en el siguiente docker compose pull. Por tanto, una configuración que funcionó la semana pasada puede dejar de coincidir con el código que la lee. Las etiquetas de SearXNG incluyen una fecha y un commit. La etiqueta de ejemplo del .env.example upstream a fecha de agosto de 2026 es 2026.3.25-541c6c3cb, así que establezca una versión concreta en .env:
SEARXNG_VERSION=2026.3.25-541c6c3cbCompruebe las etiquetas publicadas y fije la versión que realmente haya probado. Después, depure con un objetivo fijo. El mismo archivo .env contiene la clave secreta. Lea cómo funcionan los archivos de entorno y los secretos en Docker Compose antes de confirmar ese directorio en cualquier repositorio.
El limitador necesita Valkey o no se ejecuta
El limitador cuenta las solicitudes de cada cliente, y esos contadores deben compartirse entre los procesos de trabajo. Ese almacén es Valkey, la bifurcación mantenida de Redis. Las guías antiguas de SearXNG llaman a este ajuste redis:. Las versiones actuales leen valkey:, así que copie el nombre de la clave de la documentación actual y no de una publicación antigua.
use_default_settings: true
server:
secret_key: "change-this-value"
limiter: true
public_instance: false
valkey:
url: valkey://searxng-valkey:6379/0El archivo compose de upstream ya ejecuta un servicio searxng-valkey con la imagen docker.io/valkey/valkey:9-alpine, por lo que ese nombre de host se resuelve dentro de la red de compose. También puede establecer el mismo valor con la variable de entorno SEARXNG_VALKEY_URL, y una URL de socket Unix (unix:///path/to/socket.sock?db=0) funciona cuando SearXNG y Valkey comparten un host.
Lo que ocurre cuando falta el almacén depende de otra clave. Con public_instance: false, el limitador registra el error de Valkey y se detiene, por lo que la instancia sigue atendiendo solicitudes sin ningún límite de tasa. Con public_instance: true, el proceso llama a sys.exit(1) en su lugar, porque una instancia pública con la protección contra bots desactivada recibe CAPTCHA (prueba pública de Turing completamente automatizada para diferenciar ordenadores y humanos) de todos los motores en un día. Si un contenedor se reinicia en bucle justo después de establecer public_instance: true, esta es la causa, y la última línea antes de cada salida menciona Valkey.
Lo que cuenta realmente el limitador
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"
}
]Un cliente normal puede realizar 15 solicitudes dentro de una ventana de ráfaga de 20 segundos y 150 dentro de una ventana de 10 minutos. Cuando una solicitud se marca como sospechosa, el mismo cliente queda limitado a 2 por ventana de ráfaga. La última fila aplica el límite más estricto: después de 3 solicitudes marcadas dentro de una ventana de 30 días, esa dirección se redirige a la página de inicio en lugar de realizar la búsqueda, y el registro muestra BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /).
Estos números son constantes en searx/botdetection/ip_limit.py. No son opciones de configuración y limiter.toml no los expone, por lo que cambiarlos requiere editar el código fuente. Lo que /etc/searxng/limiter.toml sí controla son los prefijos de dirección usados para agrupar clientes, la lista de proxies de confianza, la comprobación opcional del token del enlace, y las listas de permitidos y bloqueados.
Una solicitud se marca como sospechosa mediante comprobaciones de cabeceras. Cada comprobación tiene un nombre que aparecerá en el registro de depuración:
http_accept: la cabeceraAcceptno contienetext/html.http_accept_encoding: la cabeceraAccept-Encodingno contiene nigzipnideflate.http_accept_language: no existe la cabeceraAccept-Language.http_connection: la cabeceraConnectiontiene el valorclose.http_user_agent: faltaUser-Agento coincide con un patrón de bot conocido.http_sec_fetch: la cabeceraSec-Fetch-ModeoSec-Fetch-Destno tiene el valor que enviaría un navegador.
Un navegador envía todas estas cabeceras. Una llamada simple a curl no envía casi ninguna, por lo que una solicitud de prueba escrita manualmente se marca en el primer intento, mientras que la misma búsqueda funciona en una pestaña del navegador. Por eso «funciona en mi navegador, pero mi script recibe 429» es el resultado normal y no un misterio.
Detrás de un proxy inverso, el limitador bloquea a todos al mismo tiempo
Esta es la forma más habitual de romper una instancia que funcionaba. SearXNG obtiene la dirección del cliente de la primera IP no confiable en X-Forwarded-For, recurre a X-Real-IP y, si no está disponible, usa la dirección que abrió la conexión. trusted_proxies en limiter.toml determina si se confía en esas cabeceras.
Si la dirección del proxy no está en esa lista, las cabeceras se ignoran y todos los visitantes llegan con la dirección del proxy. Por tanto, comparten un contador y todo el sitio queda bloqueado cuando el total supera 150 solicitudes en 10 minutos. Si un usuario recarga una página de resultados varias veces, puede bloquear el acceso para todos.
Confiar demasiado es peor. Si se incluye un rango público, cualquier visitante puede enviar su propia cabecera X-Forwarded-For y elegir una identidad nueva en cada solicitud. Esto desactiva el limitador para cualquiera que sepa intentarlo. Incluya sólo la dirección desde la que se conecta su propio proxy. En Docker, normalmente es una red puente dentro de 172.16.0.0/12, y esa línea viene comentada.
[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48
trusted_proxies = [
'127.0.0.0/8',
'::1',
'172.16.0.0/12',
]El proxy también debe enviar las cabeceras. Nginx no añade ninguna por sí solo:
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 y Traefik configuran las cabeceras reenviadas automáticamente. Con ellos, sólo debe completar la parte de trusted_proxies. Las ventajas y desventajas se explican en cómo elegir un proxy inverso para un servicio autohospedado. Para verificar cualquiera de las dos configuraciones, active debug, haga una búsqueda desde el teléfono usando datos móviles y confirme que la red de la línea del registro corresponde a la dirección del teléfono y no a la del proxy.
Tu agente recibe cuatro solicitudes de API por hora
La salida JSON está desactivada de forma predeterminada, por lo que debes añadirla al agente:
search:
formats:
- html
- jsonAhora vuelve a leer la fila del gráfico. Cada solicitud que pide un formato distinto de HTML se contabiliza en su propia ventana: 4 solicitudes por 1 hour, por dirección. Un agente de investigación agota ese límite en una sola tarea, y cada llamada posterior devuelve 429. Aumentar el límite no es una opción, porque el número está definido en el código fuente.
La solución correcta es indicar al limitador que este cliente no es desconocido. Añade su dirección a la lista de excepciones en limiter.toml:
[botdetection.ip_lists]
block_ip = []
pass_ip = [
'10.8.0.0/24',
]
pass_searxng_org = truepass_ip tiene prioridad sobre cualquier otro método, por lo que un cliente incluido en la lista de permitidos también omite las comprobaciones de cabeceras y una llamada curl sin argumentos funciona. Mantén el rango lo más pequeño posible y usa preferiblemente una subred VPN o una red de contenedores, no una red enrutable. La otra solución correcta es mantener el agente completamente fuera del acceso público: configúralo para usar la dirección del contenedor en la red interna, donde el proxy y su limitador nunca ven el tráfico. La configuración correspondiente se explica en dar a un agente de IA una capacidad de búsqueda de SearXNG.
Debes evitar apuntar el agente a una instancia pública administrada por otra persona. Es la forma más rápida de que los motores ascendentes bloqueen la dirección IP de un voluntario, y por eso el formato JSON está desactivado de forma predeterminada.
Cuando los motores te bloquean
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"
}
]Cuando un motor responde con su propio 429 o con una página CAPTCHA, SearXNG genera una excepción identificada y deja de consultar ese motor durante un tiempo. Una respuesta de demasiadas solicitudes lo suspende durante 3600 segundos. Una respuesta CAPTCHA simple o de acceso denegado lo suspende durante 1 day. Un CAPTCHA servido mediante Cloudflare lo suspende durante 15 days, el intervalo predeterminado más largo de la lista, porque esa respuesta indica que el bloqueo está en el perímetro y reintentar no ayudará.
Los fallos normales utilizan otros valores. Un tiempo de espera agotado o un error de análisis suspende el motor durante un intervalo corto derivado de search.ban_time_on_fail, que tiene un valor predeterminado de 5 segundos y está limitado por search.max_ban_time_on_fail a 120 segundos. Así, un motor lento se recupera por sí solo en un par de minutos, mientras que un motor bloqueado queda fuera de servicio durante horas. Esta diferencia explica un síntoma que muchas personas describen como aleatorio: los resultados son correctos y, después, desaparecen los resultados de un motor durante el resto de la tarde.
Conviene corregir los tiempos de espera antes de atribuir el problema a otra causa. El valor predeterminado de request_timeout es 2.0 segundos, un intervalo ajustado para un VPS pequeño situado lejos del servidor perimetral más cercano de un motor.
outgoing:
request_timeout: 3.0
max_request_timeout: 10.0
engines:
- name: bing
timeout: 5.0request_timeout es el valor predeterminado para todos los motores, max_request_timeout es el límite superior y un motor concreto puede tener su propio valor timeout. Aumentar estos valores intercambia más latencia por menos fallos. Hágalo en incrementos de medio segundo y supervise /stats/errors en lugar de saltar directamente a 10.
Si un motor está bloqueando realmente su dirección, elimínelo. Cada búsqueda espera al motor más lento. Mantener un motor suspendido permanentemente añade latencia y no devuelve resultados.
use_default_settings:
engines:
remove:
- googleAplique los cambios con docker compose restart searxng-core. Después, ejecute algunas búsquedas y vuelva a cargar /stats/errors. Si la página está vacía después de cinco minutos de uso real, el cambio ha funcionado.
Una IP de un centro de datos se tratará como la de un bot
La dirección de tu VPS pertenece a un rango de hosting, y los motores principales clasifican esos rangos como automatización. Algunos muestran un CAPTCHA para cada solicitud procedente de una dirección de ese tipo, sin importar lo correctas que sean las cabeceras ni lo lento que sea el ritmo. Ningún ajuste de settings.yml cambia esa clasificación.
Puedes cambiar los motores que consultas y decidir si tu instancia aparece públicamente. Una instancia privada utilizada por un solo hogar rara vez activa estos mecanismos. Una instancia pública en una IP de hosting acumulará suspensiones en los motores más estrictos. Ese es el comportamiento normal del software, no un fallo de configuración. SearXNG puede enviar las solicitudes a los motores a través de un proxy con outgoing.proxies o outgoing.using_tor_proxy, lo que traslada el tráfico a otra dirección. Los nodos de salida y las redes de proxies económicos tienen peor reputación que los rangos de hosting, así que es probable que ese cambio empeore los resultados.
Supervise la instancia para detectar los fallos primero
SearXNG responde en su puerto incluso cuando todos los motores están suspendidos. Por eso, una comprobación de disponibilidad que sólo supervise el código de estado permanece en verde aunque la instancia no devuelva resultados. Compruebe el contenido. Solicite una búsqueda real y busque en el cuerpo de la respuesta una palabra que espere encontrar. Supervisión de palabras clave con Uptime Kuma hace exactamente eso sin herramientas adicionales. Supervise también /stats/errors después de cada actualización de versión, porque los motores cambian su HTML y un analizador puede dejar de funcionar sin que intervenga ningún límite de tasa.
FAQ
¿Por qué SearXNG devuelve 429 a todos los visitantes después de colocarlo detrás de un proxy inverso?
Porque el limitador cuenta el proxy como cliente. SearXNG sólo lee X-Forwarded-For cuando la dirección de conexión aparece en trusted_proxies dentro de /etc/searxng/limiter.toml. Si no aparece, todos los visitantes comparten un contador y superan juntos el límite de 150 solicitudes por 10 minutos. Añada la dirección desde la que se conecta el proxy. En Docker, normalmente es el rango del bridge 172.16.0.0/12. Asegúrese también de que el proxy envíe X-Real-IP y X-Forwarded-For. No incluya nunca un rango que no controle, porque una red de confianza permite que cualquier visitante establezca esa cabecera y elija una identidad nueva en cada solicitud.
¿Cuántas solicitudes de API por hora permite el limitador de SearXNG?
Cuatro por dirección IP y por hora. Cualquier solicitud que pida un formato distinto de HTML cuenta en una ventana independiente de una hora. Ese límite se establece en searx/botdetection/ip_limit.py y no en limiter.toml, por lo que no se puede aumentar desde la configuración. Un agente o un script lo alcanza en una sola tarea. Añada la dirección del cliente a pass_ip en limiter.toml, o acceda a la instancia mediante una red interna donde el limitador no vea la solicitud.
¿Por qué los resultados de búsqueda aparecen vacíos sin ningún error 429?
Los motores están rechazando el servidor, no a los usuarios. Abra /stats/errors en su propia instancia. El archivo indica qué motor falló y por qué. Una entrada de CAPTCHA o de acceso denegado significa que ese motor bloqueó la dirección IP del servidor. SearXNG suspende entonces el motor: durante una hora después de una respuesta de demasiadas solicitudes y durante un día después de un CAPTCHA. Ningún ajuste local elimina un bloqueo del upstream. Quite los motores que bloqueen su dirección y conserve los que respondan.
¿Debo activar el limitador en una instancia privada?
Si nadie accede a la instancia salvo usted, deje limiter: false. Añade una dependencia de Valkey y bloquea sus propios scripts. Además, protege contra tráfico que no recibe. Actívelo en cuanto la instancia tenga una dirección pública, junto con public_instance: true. Esa combinación es intencionada: con public_instance: true y sin un Valkey operativo, el proceso termina con el estado 1 en lugar de ejecutarse sin protección.