Come self-hostare SearXNG con Docker Compose
Configura SearXNG su un VPS con Docker Compose: scopri settings.yml, limiter, nginx con TLS e API JSON per interrogare il motore dai tuoi script.
Cosa stai configurando
Il self-hosting di SearXNG fornisce un motore di ricerca privato eseguito sul tuo server. SearXNG è un metamotore di ricerca: riceve la tua query, interroga altri motori come Google, Bing, DuckDuckGo e Wikipedia, quindi unisce i risultati in un'unica pagina. Non viene creato alcun profilo e non viene impostato alcun cookie di tracciamento, perché l'unica macchina che conserva la tua query è la tua. Se hai trovato guide precedenti per un progetto chiamato semplicemente Searx, si tratta del progetto da cui questo è stato forkato. Non è più mantenuto da settembre 2023, quando il relativo repository ha annunciato che Searx non è più mantenuto; anche l'unico commit successivo, una nota nella documentazione pubblicata a maggio 2026, non ha cambiato questa situazione. Quindi verifica lo stato di entrambi prima di seguirne uno.
Lo stack è ridotto. Due container, un file di configurazione e un reverse proxy. Può condividere senza problemi un VPS di dimensioni contenute, cosa che non vale per tutti i servizi self-hosted: le librerie fotografiche confrontate in PhotoPrism e Immich richiedono una quantità minima di RAM determinata dall'indexer, non dall'applicazione web. La decisione principale riguarda la visibilità dell'istanza: privata, se possono raggiungerla soltanto i tuoi script e tu, oppure pubblica, se chiunque su Internet può inviarle query. Questa scelta modifica le impostazioni di sicurezza, quindi prendila prima di digitare qualsiasi comando. L'impostazione predefinita è privata.
Esiste un secondo motivo per eseguirne un'istanza. Un'istanza SearXNG espone un'API JSON, quindi qualsiasi script o agente AI che scrivi può usare un'API di ricerca sotto il tuo controllo, senza chiavi, costi per query o messaggi relativi alle quote.
Installare SearXNG con Docker Compose
Il progetto pubblica un'immagine del container e un file Compose. Scaricateli su un server Ubuntu 24.04 appena installato, che disponga già di Docker Engine e del plugin Compose. Se Docker è una novità, iniziate da Nozioni di base su Docker Compose su un VPS e poi tornate qui.
sudo install -d -o "$USER" -g "$USER" -m 750 /opt/searxng
cd /opt/searxng
mkdir -p core-config
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 .envIl file Compose definisce due servizi. core è SearXNG, mentre valkey è un archivio dati in memoria usato per il rate limiting e per lo stato temporaneo. Il file monta ./core-config/ in /etc/searxng/ all'interno del container, quindi tutta la configurazione risiede in un'unica directory sull'host.
Ora modificate .env. Nel file di esempio fornito, ogni riga è commentata. Per questo motivo il container si avvia sulla porta 8080 su tutti gli indirizzi. Rimuovete il commento e impostate questi tre valori.
SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080SEARXNG_HOST=127.0.0.1 è il parametro più importante. Pubblica la porta 127.0.0.1:8080:8080 invece di un semplice 8080:8080, che Docker pubblica su tutti gli indirizzi. In questo modo il container risponde soltanto sull'indirizzo di loopback e Internet non può raggiungerlo direttamente. Se omettete questo parametro, il container viene esposto appena si avvia, perché una porta pubblicata da Docker viene inserita prima delle regole del firewall. Questo problema merita una lettura completa: le porte Docker pubblicate bypassano ufw.
SEARXNG_VERSION=latest va bene durante la fase di apprendimento. Su un server importante, bloccate il tag su una versione specifica. A ottobre 2026, i tag delle release sono basati sulla data e hanno un formato simile a 2026.10.4-d48c4b555. In questo modo una distribuzione con versione bloccata viene aggiornata quando lo decidete voi, non quando il registry cambia senza preavviso. La stessa disciplina è utile per qualsiasi altro componente a lunga durata sul server. Per questo anche un relay RustDesk self-hosted usa tag dell'immagine bloccati: un aggiornamento automatico di un servizio di accesso remoto si manifesta nel momento peggiore.
settings.yml: gli elementi importanti
Crea core-config/settings.yml prima del primo avvio. use_default_settings: true indica a SearXNG di caricare i valori predefiniti inclusi nel pacchetto e di applicare soltanto le chiavi specificate. In questo modo il file resta breve e continua a funzionare anche dopo gli aggiornamenti che aggiungono nuove opzioni.
Genera prima il secret, perché il valore viene inserito direttamente nel file.
openssl rand -hex 32use_default_settings: true
general:
instance_name: "search.example.com"
server:
base_url: "https://search.example.com/"
secret_key: "paste-the-openssl-output-here"
limiter: false
public_instance: false
image_proxy: true
valkey:
url: valkey://valkey:6379/0
search:
safe_search: 0
autocomplete: "duckduckgo"
formats:
- html
- jsonsecret_key firma i dati delle sessioni e dei token. Il valore predefinito incluso nel pacchetto è la stringa letterale ultrasecretkey. Se la lasci invariata, chiunque conosca quel valore può falsificare questi token. Sostituiscila una sola volta e poi non modificarla più: cambiarla in seguito elimina tutte le preferenze salvate.
base_url deve essere l'indirizzo HTTPS pubblico, con la barra finale. SearXNG lo inserisce nei link che genera. Se lo lasci impostato su localhost, il link "pagina successiva" visualizzato in un browser remoto punta alla macchina del lettore e non funziona.
formats determina i tipi di output che l'endpoint web può produrre. json non è incluso nell'elenco predefinito, quindi una richiesta JSON restituisce 403 finché non lo aggiungi. image_proxy: true instrada le miniature dei risultati attraverso il tuo server, impedendo ai siti che ospitano quelle immagini di vedere gli indirizzi dei visitatori.
Il valkey.url usa l'hostname valkey perché questo è il nome del servizio nel file Compose. Compose inserisce entrambi i container nella stessa rete, dove i nomi dei servizi vengono risolti. Se lo punti a localhost, il limitatore non funziona, perché all'interno del container core localhost identifica quel container.
Il secret si trova in un file normale, quindi devi proteggere anche la directory che lo contiene. chmod 750 /opt/searxng impedisce l'accesso agli altri utenti dell'host. Puoi anche impostare core-config/settings.yml sulla modalità 600 senza interrompere il funzionamento: l'entrypoint del container ufficiale viene avviato come root e, con FORCE_OWNERSHIP lasciato al valore predefinito true, assegna la directory di configurazione montata all'utente searxng del container prima di avviare SearXNG. Il file resta quindi leggibile da quell'utente. Sul sistema host, però, la stessa operazione modifica il proprietario di core-config/ impostandolo sull'ID utente del container. Dopo questa modifica, prevedi di dover modificare il file con sudo.
Avvia lo stack e controllalo.
cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/docker compose ps dovrebbe mostrare entrambi i container nello stato running. Il curl dovrebbe rispondere con HTTP/1.1 200 OK. Se non risponde, leggi docker compose logs core: un errore YAML in settings.yml viene mostrato lì come errore di analisi con l'indicazione della riga.
Configurarlo dietro nginx con TLS
Il container è in ascolto solo sull'interfaccia di loopback. nginx lo rende raggiungibile e aggiunge anche la sicurezza del livello di trasporto (TLS). Scrivere /etc/nginx/sites-available/searxng.
server {
listen 80;
server_name search.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}sudo ln -s /etc/nginx/sites-available/searxng /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d search.example.comnginx -t stampa syntax is ok e test is successful prima del reload. Certbot riscrive lo stesso file per l'ascolto sulla porta 443 con un certificato e aggiunge un redirect dalla porta 80. Il record DNS di search.example.com deve già puntare a questo server, perché l'autorità di certificazione verifica la proprietà recuperando un file tramite HTTP. La procedura completa, incluso il rinnovo, è disponibile nella guida a Certbot e nginx per Ubuntu 24.04.
I due header di inoltro non sono decorativi. Senza X-Forwarded-For e X-Real-IP, ogni richiesta che arriva a SearXNG contiene l'indirizzo del proxy. Di conseguenza, il rate limiter rileva un solo client che genera tutto il traffico e non riesce a distinguere i visitatori.
Perché script e agenti richiedono un'API di ricerca JSON
Con json in formats, lo stesso endpoint che restituisce la pagina fornisce dati strutturati.
curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
| jq -r '.results[0:5][] | .url'Ricevi un oggetto con un array results, in cui ogni voce contiene url, title, content e il motore che ha fornito il risultato, oltre a answers, infoboxes e suggestions. È sufficiente per alimentare un sistema di riepilogo, un controllo dei link o un ciclo di ricerca. Passare questi risultati a un modello linguistico è un'operazione più delicata di quanto sembri, perché i risultati della ricerca sono testo non attendibile e possono contenere istruzioni proprie. Questo è il problema affrontato in dettaglio da indirizzare un agente AI alla tua istanza SearXNG.
Questo è importante per qualsiasi applicazione basata su agenti. Un modello linguistico ha una data limite per i dati di addestramento, quindi ha bisogno della ricerca in tempo reale per rispondere a domande sul presente. Le API di ricerca commerciali applicano un costo per query e impongono limiti di frequenza rigidi. Un'istanza locale richiede un solo container su un server che già paghi e le query non lasciano mai la tua infrastruttura. Se colleghi strumenti a un modello, lo stesso principio porta a eseguire server MCP su un VPS, dove uno strumento di ricerca è spesso il primo che viene aggiunto.
Per l'uso delle API valgono due regole. Mantieni privata l'istanza: associa il componente API all'indirizzo di loopback o a una rete privata e consenti l'accesso soltanto ai tuoi host. È lo stesso approccio richiesto da un tracker di posizione self-hosted per l'endpoint a cui il telefono invia la propria posizione. Poi invia le query con moderazione. SearXNG inoltra la richiesta ai motori di ricerca reali, quindi uno script che esegue cento query al secondo sta chiedendo a Google di bloccare il tuo server.
Il limiter e cosa cambia per un'istanza pubblica
Il limiter è il sistema anti-bot di SearXNG. Controlla gli header delle richieste, gli indirizzi e la frequenza delle richieste, quindi scarta il traffico che sembra automatizzato. Per mantenere questo stato usa Valkey, motivo per cui è incluso nel file Compose.
In un'istanza privata mantieni limiter: false. I tuoi script sono per definizione traffico automatizzato, quindi il limiter bloccherebbe proprio le chiamate JSON per cui hai creato l'istanza. Il controllo degli accessi spetta invece al reverse proxy: puoi usare una coppia allow e deny nel file nginx location, l'autenticazione HTTP di base oppure un firewall che consenta l'accesso soltanto agli altri tuoi server. Se devi raggiungere un'istanza privata da un laptop che cambia rete, anteporre un indirizzo onion v3 è una quarta opzione, perché Tor si connette alla stessa porta di loopback senza esporre altro su Internet.
Se pubblichi l'istanza per altre persone, attiva entrambi gli switch.
server:
limiter: true
public_instance: trueUn controllo più preciso si configura in core-config/limiter.toml, che il container legge da /etc/searxng/limiter.toml. Devi scrivere soltanto le chiavi che vuoi modificare. Se usi un proxy, devi dichiararlo; in caso contrario il limiter considera l'indirizzo di nginx come quello dell'unico client che genera traffico abusivo.
[botdetection]
trusted_proxies = [
'127.0.0.0/8',
'::1',
]
[botdetection.ip_limit]
link_token = truelink_token = true fa sì che SearXNG emetta un token che soltanto una sessione reale del browser può recuperare, bloccando la maggior parte degli scraper più semplici. Un'istanza pubblica attirerà questi strumenti nel giro di pochi giorni. Devi aspettarti anche errori dei motori di ricerca, perché più traffico inoltri, prima i motori upstream inizieranno a restituire CAPTCHA al tuo indirizzo server. Un'istanza SearXNG pubblica richiede attività continua. Un'istanza privata no, motivo per cui compare nella maggior parte degli elenchi brevi di servizi che vale la pena ospitare autonomamente nel 2026. Inoltre, non tutte le voci di questi elenchi sono infrastruttura: ricostruire una libreria Jellyfin come un videonoleggio degli anni 90 visitabile significa usare lo stesso container dietro lo stesso blocco nginx, ma orientato a una serata invece che a un flusso di lavoro.
Perché le ricerche non restituiscono risultati
Apri /stats sulla tua istanza. Elenca tutti i motori con il relativo tasso di errore e tempo di risposta. È il primo punto da controllare quando i risultati sono insufficienti.
Un motore con errori "Access denied" o "CAPTCHA" ha bloccato l'indirizzo del tuo server. È comune per gli indirizzi appartenenti a intervalli usati dai data center, perché i motori di ricerca li associano ai crawler. SearXNG sospende quindi il motore in errore per un certo periodo, invece di riprovare. Un motore bloccato scompare così dai risultati senza messaggi evidenti. Disabilitalo in settings.yml oppure accetta di perderlo. Non sono le uniche possibilità, perché alcuni blocchi CAPTCHA possono essere risolti in modo permanente anche dopo un riavvio. Gli altri motori continuano a rispondere. Un errore 429 è il caso ambiguo: può essere causato dal limiter locale oppure da un motore upstream che rifiuta il server. La riga del log indica quale delle due cause si è verificata prima che tu inizi a modificare le impostazioni.
Se tutti i motori falliscono contemporaneamente, il container non dispone di una risoluzione dei nomi in uscita funzionante oppure non ha una route verso Internet. Esegui il test dall'interno del container.
docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo okIl sistema non registra quando questo controllo inizia a fallire. Eseguilo quindi tramite cron e lascia che un errore invi un alert al tuo telefono dal tuo server ntfy invece di aspettare di accorgerti che i risultati sono diventati insufficienti.
FAQ
SearXNG rende anonime le mie ricerche?
Nasconde la tua identità ai motori a cui inoltra le richieste, perché questi vedono il tuo server effettuare la richiesta invece del browser. Non nasconde la query al tuo server e non nasconde il tuo server ai motori. In un'istanza utilizzata da una sola persona, tutto il traffico proveniente da quell'indirizzo è tuo, quindi l'indirizzo stesso diventa l'identificatore. Il traffico tra il browser e la tua istanza è protetto dal certificato TLS. Il livello di protezione che ottieni rispetto al tuo ISP, al gestore di un'istanza pubblica e ai motori è descritto in cosa nasconde realmente SearXNG.
Perché una richiesta JSON restituisce 403 Forbidden?
Le cause sono due e dipendono entrambe dalla configurazione. Oppure json manca dall'elenco formats sotto search: in settings.yml, che è lo stato predefinito, oppure il limiter è attivo e ha classificato lo script come bot. Aggiungi prima il formato, riavvia con docker compose restart core, quindi riprova. Se il problema persiste, imposta limiter: false e controlla l'accesso nel reverse proxy.
Ho bisogno del container Valkey se lascio disattivato il limiter?
Lascialo in esecuzione. SearXNG funziona anche senza, ma il limiter non può essere attivato in seguito senza di esso e il container conserva anche altro stato temporaneo. Il container è di piccole dimensioni e memorizza solo dati nella cache, quindi rimuoverlo fa risparmiare molto poco e ti fa perdere questa possibilità.
Come aggiorno SearXNG?
Esegui docker compose pull e poi docker compose up -d in /opt/searxng. Compose ricrea ogni container la cui immagine è cambiata e lascia invariata la directory core-config/, quindi settings.yml rimane disponibile. Poiché use_default_settings: true sovrappone le tue chiavi ai valori predefiniti forniti, le opzioni aggiunte upstream arrivano con valori appropriati invece di causare la rottura del file.
Più persone possono condividere la stessa istanza?
Sì. Questo è il caso in cui devi attivare il limiter e impostare public_instance: true. Le preferenze vengono memorizzate nel browser di ogni visitatore, quindi non devi gestire account. Monitora /stats per una settimana dopo averla resa accessibile, perché i motori upstream iniziano a rifiutare le richieste del tuo server molto prima che tu noti l'assenza di risultati.