SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor

SearXNG: risolvere errori 429 e limiti di frequenza

Scopri se il 429 di SearXNG viene dal limitatore interno o dal blocco dell'IP da parte dei motori, leggendo il log prima di cambiare configurazione.

Perché SearXNG restituisce errori 429

Un'istanza SearXNG self-hosted restituisce errori 429 per due motivi distinti. Il limite di frequenza da correggere di solito non è quello che si presume. Il primo motivo è locale: il limitatore interno di SearXNG ha stabilito che una richiesta proveniva da un bot e ha risposto Too Many Requests con lo stato 429. Il secondo è upstream: un motore di ricerca ha rifiutato l'indirizzo IP del server. Agli utenti questo problema appare come una pagina dei risultati con elementi mancanti, non come un errore 429.

I due casi non hanno una soluzione in comune. Il limitatore è sotto il tuo controllo, quindi puoi modificarlo. Il blocco upstream avviene lato Google, quindi nessuna modifica nel tuo settings.yml lo rimuoverà. Dai log puoi capire quale dei due casi si verifica in circa un minuto. Inizia da lì.

Questa guida presuppone l'installazione tramite container descritta in un'istanza SearXNG self-hosted sul tuo VPS. Tutti i nomi delle impostazioni riportati di seguito provengono dalla documentazione e dal codice sorgente upstream aggiornati, verificati ad agosto 2026.

Leggi il log prima di modificare un'impostazione

Riproduci il problema con una finestra del log aperta.

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

I messaggi del limiter provengono dal logger denominato searx.limiter e riportano un indirizzo IP. Un riscontro nella blocklist appare come BLOCK 203.0.113.10: matched BLOCKLIST, mentre un riscontro nell'allowlist appare come PASS 203.0.113.10: matched PASSLIST. Se il limiter non riesce a raggiungere il proprio archivio dei contatori, il log riporta The limiter requires Valkey, please consult the documentation. Questo significa che non viene conteggiato nulla.

Ogni controllo dei bot viene registrato al livello debug, quindi per impostazione predefinita non lo vedrai. Abilita il debug per un solo test in settings.yml:

general:
  debug: true

Il log aggiunge quindi righe nel formato NOT OK (http_accept_language) accanto alla rete client e indica il controllo che ha restituito un errore. Al termine disabilita nuovamente il debug, perché il progetto upstream raccomanda di non eseguire in produzione un'istanza con il debug attivo.

Gli errori del motore hanno un formato completamente diverso. Indicano un motore invece di un IP e, nella maggior parte dei casi, segnalano un timeout:

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

Esiste anche una pagina dedicata. Se enable_metrics mantiene il valore predefinito true, l'istanza registra gli errori dei motori in /stats/errors e /preferences elenca i motori che stanno attualmente rispondendo. Se /stats/errors è pieno e nel log non compaiono righe searx.limiter, il problema non è nel limiter.

Fissa la versione prima di eseguire qualsiasi operazione di debug

La configurazione upstream del container è composta da due file.

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

Il file Compose esegue il pull di docker.io/searxng/searxng:${SEARXNG_VERSION:-latest}. Una variabile non impostata significa latest, mentre latest significa che l’istanza cambia al successivo docker compose pull. Di conseguenza, un’impostazione che funzionava la settimana scorsa può non corrispondere più al codice che la legge. I tag di SearXNG includono una data e un commit. Il tag di esempio nella configurazione upstream .env.example ad agosto 2026 è 2026.3.25-541c6c3cb. Imposta quindi un valore reale in .env:

SEARXNG_VERSION=2026.3.25-541c6c3cb

Controlla i tag pubblicati e fissa la release che hai effettivamente testato. Esegui quindi il debug su una versione invariata. Lo stesso file .env contiene la chiave segreta. Prima di eseguire il commit di quella directory in qualsiasi repository, leggi come funzionano i file env e i secret in Docker Compose.

Il limitatore richiede Valkey, altrimenti non viene eseguito

Il limitatore conta le richieste per client e questi conteggi devono essere condivisi tra i processi worker. Il datastore utilizzato è Valkey, il fork mantenuto di Redis. Le guide meno recenti di SearXNG chiamano questa impostazione redis:. Le release attuali leggono valkey:; copia quindi il nome della chiave dalla documentazione corrente, non da un articolo più vecchio.

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

Il file compose upstream esegue già un servizio searxng-valkey sull'immagine docker.io/valkey/valkey:9-alpine, quindi quel nome host viene risolto nella rete compose. Lo stesso valore può essere impostato con la variabile d'ambiente SEARXNG_VALKEY_URL. Un URL per socket Unix (unix:///path/to/socket.sock?db=0) funziona quando SearXNG e Valkey condividono lo stesso host.

Il comportamento in assenza del datastore dipende da un'altra chiave. Con public_instance: false, il limitatore registra l'errore di Valkey e interrompe il tentativo, quindi l'istanza continua a servire le richieste senza alcun rate limiting. Con public_instance: true, il processo chiama invece sys.exit(1), perché un'istanza pubblica con la protezione contro i bot non funzionante riceve CAPTCHA (test di Turing pubblico completamente automatizzato per distinguere computer e persone) da ogni motore entro un giorno. Se un container entra in un ciclo di riavvii subito dopo che hai impostato public_instance: true, la causa è questa; l'ultima riga prima di ogni uscita indica Valkey.

Cosa conta effettivamente il limitatore

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"
  }
]

Un client normale può effettuare 15 richieste in una finestra di burst di 20 secondi e 150 richieste in una finestra di 10 minuti. Quando una richiesta viene contrassegnata come sospetta, lo stesso client scende a 2 richieste per finestra di burst. L’ultima riga applica la regola più severa: dopo 3 richieste contrassegnate in una finestra di 30 giorni, quell’indirizzo viene reindirizzato alla pagina iniziale invece di eseguire la ricerca e nel log compare BLOCK: too many request from ... in SUSPICIOUS_IP_WINDOW (redirect to /).

Questi numeri sono costanti in searx/botdetection/ip_limit.py. Non sono impostazioni e limiter.toml non le espone. Per modificarle è quindi necessario intervenire sul codice sorgente. /etc/searxng/limiter.toml controlla invece i prefissi degli indirizzi usati per raggruppare i client, l’elenco dei proxy attendibili, il controllo opzionale del token del link e gli elenchi di indirizzi consentiti e bloccati.

Una richiesta viene contrassegnata come sospetta in base ai controlli sugli header. Ogni controllo ha un nome, visibile nel log di debug:

  • http_accept: l’header Accept non contiene text/html.
  • http_accept_encoding: l’header Accept-Encoding non indica né gzipdeflate.
  • http_accept_language: l’header Accept-Language non è presente.
  • http_connection: l’header Connection è impostato su close.
  • http_user_agent: User-Agent non è presente oppure corrisponde a un pattern noto di bot.
  • http_sec_fetch: l’header Sec-Fetch-Mode o Sec-Fetch-Dest non ha il formato inviato da un browser.

Un browser invia tutti questi elementi. Una semplice chiamata curl ne invia quasi nessuno. Per questo una richiesta di test scritta manualmente viene contrassegnata al primo tentativo, mentre la stessa ricerca funziona in una scheda del browser. Ecco perché il messaggio «nel browser funziona, ma il mio script riceve 429» è il risultato normale, non un comportamento misterioso.

Il limiter blocca tutti contemporaneamente dietro un reverse proxy

Questo è il modo più comune per compromettere un'istanza funzionante. SearXNG ricava l'indirizzo client dal primo IP non attendibile in X-Forwarded-For, usa X-Real-IP come fallback e, se necessario, risale all'indirizzo che ha aperto la connessione. La decisione di considerare attendibili questi header dipende da trusted_proxies in limiter.toml.

Se l'indirizzo del proxy non è incluso nell'elenco, gli header vengono ignorati e ogni visitatore risulta avere l'indirizzo del proxy. Tutti condividono quindi lo stesso contatore e l'intero sito viene bloccato quando il totale supera 150 richieste in 10 minuti. Basta che un utente ricarichi alcune volte una pagina dei risultati per bloccare tutti gli altri.

Una configurazione eccessivamente permissiva è ancora peggiore. Se viene indicata una subnet pubblica, ogni visitatore può inviare il proprio header X-Forwarded-For e scegliere un'identità diversa per ogni richiesta, disattivando di fatto il limiter per chiunque sappia sfruttare il problema. Indica soltanto l'indirizzo da cui si connette il tuo proxy. In Docker si tratta in genere di una rete bridge interna a 172.16.0.0/12, e quella riga è commentata per impostazione predefinita.

[botdetection]
ipv4_prefix = 32
ipv6_prefix = 48

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

Anche il proxy deve inviare gli header. Nginx non ne aggiunge nessuno automaticamente:

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 e Traefik impostano automaticamente gli header inoltrati, quindi con questi proxy devi configurare soltanto la parte trusted_proxies. I compromessi sono descritti in come scegliere un reverse proxy per un servizio self-hosted. Per verificare entrambe le configurazioni, abilita debug, esegui una ricerca dal telefono usando la rete mobile e verifica che nella riga di log compaia l'indirizzo del telefono, non quello del proxy.

L’agente riceve quattro richieste API all’ora

L’output JSON è disabilitato per impostazione predefinita, quindi è necessario aggiungerlo per l’agente:

search:
  formats:
    - html
    - json

Ora rileggi la riga della tabella. Ogni richiesta che richiede un formato diverso da HTML viene conteggiata nella propria finestra: 4 richieste per 1 hour, per indirizzo. Un agente di ricerca esaurisce questo limite in una sola attività e ogni chiamata successiva restituisce 429. Aumentare il limite non è un’opzione, perché il valore è definito nel codice sorgente.

La correzione appropriata consiste nel comunicare al limiter che questo client non è sconosciuto. Aggiungi il suo indirizzo all’elenco degli indirizzi autorizzati in limiter.toml:

[botdetection.ip_lists]
block_ip = []

pass_ip = [
  '10.8.0.0/24',
]

pass_searxng_org = true

pass_ip ha la precedenza su ogni altro metodo, quindi un client autorizzato ignora anche i controlli sugli header e una chiamata curl senza argomenti funziona. Mantieni l’intervallo più ristretto possibile e preferisci una subnet VPN o una rete di container a qualsiasi rete instradabile. L’altra soluzione corretta consiste nel tenere l’agente completamente fuori dal percorso pubblico: indirizzalo all’indirizzo del container sulla rete interna, dove il proxy e il relativo limiter non vedono il traffico. La configurazione è descritta in assegnare a un agente AI una competenza di ricerca SearXNG.

L’opzione da evitare consiste nell’indirizzare l’agente a un’istanza pubblica gestita da terzi. È il modo più rapido per far bloccare dai motori upstream l’indirizzo IP di un volontario e per questo il formato JSON è disabilitato per impostazione predefinita.

Quando sono i motori a bloccarvi

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"
  }
]

Quando un motore risponde con un proprio errore 429 o con una pagina CAPTCHA, SearXNG genera un'eccezione nominata e smette per un certo periodo di interrogarlo. Una risposta di tipo troppe richieste lo sospende per 3600 secondi. Una semplice risposta CAPTCHA o di accesso negato lo sospende per 1 day. Un CAPTCHA fornito tramite Cloudflare lo sospende per 15 days, il valore predefinito più lungo dell'elenco, perché indica che il blocco si trova sul perimetro della rete e che riprovare non sarà utile.

Gli errori ordinari usano impostazioni diverse. Un timeout o un errore di analisi sospende il motore per un breve periodo derivato da search.ban_time_on_fail, che per impostazione predefinita è di 5 secondi ed è limitato da search.max_ban_time_on_fail a 120 secondi. Un motore lento quindi torna operativo autonomamente entro un paio di minuti, mentre un motore bloccato resta inattivo per ore. Questa differenza spiega un sintomo che molti descrivono come casuale: i risultati sono normali, poi quelli di un motore scompaiono per il resto del pomeriggio.

Conviene risolvere i timeout prima di attribuire la colpa a qualcuno. Il valore predefinito di request_timeout è 2.0 secondi, un limite ristretto per un piccolo VPS lontano dal server edge più vicino del motore.

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

request_timeout è il valore predefinito per ogni motore, max_request_timeout è il limite massimo e un singolo motore può avere un proprio timeout. Aumentare questi valori riduce gli errori, ma aumenta la latenza delle pagine. Procedete quindi per incrementi di mezzo secondo e monitorate /stats/errors invece di passare direttamente a 10.

Se un motore sta realmente bloccando il vostro indirizzo, rimuovetelo. Ogni ricerca attende il motore più lento, quindi mantenere un motore permanentemente sospeso aumenta la latenza e non restituisce risultati.

use_default_settings:
  engines:
    remove:
      - google

Applicate le modifiche con docker compose restart searxng-core, quindi eseguite alcune ricerche e ricaricate /stats/errors. Se dopo cinque minuti di utilizzo effettivo la pagina è vuota, la modifica ha avuto effetto.

Un indirizzo IP di un datacenter verrà trattato come un bot

L'indirizzo del tuo VPS appartiene a un intervallo di hosting, e i principali motori classificano questi intervalli come traffico automatizzato. Alcuni mostrano un CAPTCHA per ogni richiesta proveniente da un indirizzo di questo tipo, indipendentemente dalla correttezza degli header o dalla lentezza delle richieste. Nessuna impostazione in settings.yml modifica questa classificazione.

Puoi però scegliere quali motori interrogare e decidere se rendere pubblica la tua istanza. Un'istanza privata usata da un solo nucleo familiare raramente attiva questi controlli. Un'istanza pubblica su un IP di hosting accumulerà sospensioni sui motori più restrittivi. Questo è il comportamento normale del software, non un errore nella configurazione. SearXNG può instradare le richieste ai motori tramite un proxy con outgoing.proxies o outgoing.using_tor_proxy, trasferendo il traffico a un indirizzo diverso. I nodi di uscita e i pool di proxy economici ricevono valutazioni peggiori rispetto agli intervalli di hosting. È quindi probabile che questa modifica peggiori i risultati.

Monitora l'istanza per rilevare subito i problemi

SearXNG risponde sulla propria porta anche quando tutti i motori sono sospesi. Di conseguenza, un controllo di disponibilità che verifica soltanto il codice di stato resta verde, mentre l'istanza non restituisce alcun risultato. Controlla invece il contenuto: esegui una ricerca reale e verifica la presenza di una parola prevista nel corpo della risposta. Monitoraggio delle parole chiave con Uptime Kuma esegue esattamente questo controllo senza strumenti aggiuntivi. Monitora anche /stats/errors dopo ogni aggiornamento di versione, perché i motori modificano il proprio HTML e un parser può rompersi senza che sia coinvolto alcun rate limit.

FAQ

Perché SearXNG restituisce 429 a ogni visitatore dopo la configurazione dietro un reverse proxy?

Perché il limiter considera il proxy come client. SearXNG legge X-Forwarded-For solo quando l'indirizzo di connessione è elencato in trusted_proxies in /etc/searxng/limiter.toml. Se l'indirizzo non è elencato, tutti i visitatori condividono lo stesso contatore e superano insieme la soglia di 150 richieste in 10 minuti. Aggiungi l'indirizzo da cui si connette il proxy. In Docker, di solito è l'intervallo bridge 172.16.0.0/12. Verifica inoltre che il proxy invii X-Real-IP e X-Forwarded-For. Non elencare mai un intervallo che non controlli. Una rete considerata attendibile consente infatti a qualsiasi visitatore di impostare quell'header e scegliere una nuova identità per ogni richiesta.

Quante richieste API all'ora consente il limiter di SearXNG?

Quattro per indirizzo IP all'ora. Ogni richiesta che richiede un formato diverso da HTML viene conteggiata in una finestra separata di un'ora. Questo limite è impostato in searx/botdetection/ip_limit.py e non in limiter.toml, quindi non può essere aumentato dalla configurazione. Un agent o uno script raggiunge il limite con una sola attività. Aggiungi l'indirizzo del client a pass_ip in limiter.toml oppure raggiungi l'istanza tramite una rete interna, dove il limiter non vede la richiesta.

Perché i risultati delle ricerche sono vuoti senza un errore 429?

Sono i motori a rifiutare il server, non gli utenti. Apri /stats/errors nella tua istanza. Il file indica ogni motore che ha restituito un errore e la relativa causa. Una voce CAPTCHA o di accesso negato indica che il motore ha bloccato l'indirizzo IP del server. SearXNG sospende quindi il motore: per un'ora dopo una risposta di troppe richieste e per un giorno dopo un CAPTCHA. Nessuna impostazione locale può rimuovere un blocco applicato dal servizio a monte. Rimuovi quindi i motori che bloccano il tuo indirizzo e mantieni quelli che rispondono.

Devo abilitare il limiter su un'istanza privata?

Se all'istanza accedi solo tu, lascia limiter: false. Il limiter aggiunge una dipendenza da Valkey e blocca i tuoi script, proteggendo da traffico che non riceveresti. Abilitalo non appena l'istanza diventa pubblicamente raggiungibile, insieme a public_instance: true. Questa coppia di impostazioni è intenzionale: con public_instance: true e Valkey non funzionante, il processo termina con stato 1 invece di funzionare senza protezione.