SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-09-05

Come creare un motore di ricerca privato con SearXNG

Installa SearXNG su un VPS con Docker Compose: configura settings.yml, limiter e nginx con TLS, poi usa l'API JSON per le tue automazioni.

Cosa stai configurando

Il self-hosting di SearXNG ti 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 meno recenti per qualcosa chiamato semplicemente Searx, si tratta del progetto da cui questo è stato forkato e che non riceve commit dal 2023; quindi controlla lo stato di entrambi prima di sceglierne uno.

Lo stack è ridotto. Due container, un file di configurazione e un reverse proxy. Può condividere senza problemi un VPS di piccole dimensioni, cosa che non vale per tutti i servizi self-hosted: le librerie fotografiche confrontate in PhotoPrism e Immich fissano il requisito minimo di RAM in base all'indexer, non all'applicazione web. La decisione principale riguarda la visibilità dell'istanza: privata, cioè raggiungibile soltanto da te e dai tuoi script, oppure pubblica, cioè interrogabile da chiunque su Internet. Questa scelta modifica le impostazioni di sicurezza, quindi prendila prima di digitare qualsiasi comando. La risposta predefinita è privata.

C'è un secondo motivo per eseguire un'istanza. SearXNG espone un'API JSON, quindi qualsiasi script o agente AI che scrivi può usare un'API di ricerca di tua proprietà, senza chiavi, costi per query o messaggi relativi alle quote.

Installare SearXNG con Docker Compose

Il progetto pubblica un'immagine container e un file Compose. Scaricali su un server Ubuntu 24.04 appena installato, su cui Docker Engine e il plugin Compose siano già disponibili. Se Docker è una novità, inizia da Nozioni di base su Docker Compose su un VPS e poi torna 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 .env

Il file Compose definisce due servizi. core è SearXNG, mentre valkey è un archivio dati in memoria usato per il rate limiting e per lo stato a breve durata. Esegue il mount di ./core-config/ in /etc/searxng/ all'interno del container, quindi tutta la configurazione risiede in quell'unica directory sull'host.

Ora modifica .env. Ogni riga dell'esempio fornito è commentata. Per questo il container si avvia sulla porta 8080 su tutti gli indirizzi. Decommenta e imposta questi tre valori.

SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080

SEARXNG_HOST=127.0.0.1 è il parametro più importante. Pubblica la porta 127.0.0.1:8080:8080 invece di [::]:8080:8080, quindi il container risponde solo sull'indirizzo di loopback e Internet non può raggiungerlo direttamente. Se ometti questa impostazione, il container viene esposto non appena si avvia, perché una porta Docker pubblicata viene inserita prima delle regole del firewall. Questo comportamento merita una lettura completa: le porte Docker pubblicate bypassano ufw.

SEARXNG_VERSION=latest è sufficiente durante la fase di apprendimento. Su un server importante, blocca il tag su una versione specifica. A luglio 2026 i tag delle release si basano sulla data e hanno un formato simile a 2026.3.25-541c6c3cb. In questo modo il deployment si aggiorna quando lo decidi tu, non quando il registry cambia senza preavviso. La stessa disciplina è utile per qualsiasi altro servizio destinato a rimanere a lungo sul server. Per questo un relay RustDesk self-hosted blocca a sua volta i tag delle immagini: un aggiornamento non supervisionato di un servizio di accesso remoto si manifesta nel momento peggiore.

settings.yml: le parti 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 solo le chiavi specificate nel file. 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 32
use_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
    - json

secret_key firma i dati delle sessioni e dei token. Il valore predefinito incluso nel pacchetto è la stringa letterale ultrasecretkey. Se lo lasci invariato, chiunque conosca quel valore predefinito può contraffare quei token. Sostituiscilo una sola volta e poi non modificarlo più: cambiarlo 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" in un browser remoto punta al computer dell'utente e non funziona.

formats determina i tipi di output che l'endpoint web produrrà. json non è incluso nell'elenco predefinito, quindi una richiesta JSON restituisce 403 finché non lo aggiungi. image_proxy: true inoltra le miniature dei risultati tramite il tuo server, così i siti che ospitano quelle immagini non vedono gli indirizzi dei tuoi visitatori.

Il valkey.url usa il nome host valkey perché questo è il nome del servizio nel file Compose. Compose colloca entrambi i container sulla stessa rete, dove i nomi dei servizi vengono risolti. Se lo imposti su localhost, il limiter non funziona, perché all'interno del container core localhost identifica quel container.

Il secret si trova in un file in chiaro. Proteggi quindi la directory che lo contiene, anziché il file stesso. chmod 750 /opt/searxng impedisce l'accesso agli altri utenti dell'host. Non restringere core-config/settings.yml alla modalità 600: il container viene eseguito con un proprio utente senza privilegi e, se non può leggere il file, SearXNG non si avvia.

Avvia lo stack e verifica il risultato.

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 su HTTP/1.1 200 OK. Se non risponde, consulta docker compose logs core, perché un errore YAML in settings.yml viene mostrato lì come errore di analisi con l'indicazione della riga.

Posizionalo dietro nginx con TLS

Il container è in ascolto solo sull'interfaccia di loopback, quindi nginx lo rende raggiungibile e aggiunge anche la sicurezza del livello di trasporto (TLS). Scrivi /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.com

nginx -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 per search.example.com deve puntare già a questo server, perché l'autorità di certificazione dimostra 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, quindi il rate limiter vede un solo client che genera tutto il traffico e non può distinguere i visitatori.

Perché gli script e gli agenti richiedono un'API di ricerca JSON

Con json in formats, lo stesso endpoint che visualizza la pagina restituisce dati strutturati.

curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
  | jq -r '.results[0:5][] | .url'

Viene restituito un oggetto con un array results, in cui ogni voce contiene url, title, content e il motore che l'ha fornita, oltre a answers, infoboxes e suggestions. È sufficiente per alimentare un sistema di riepilogo, un verificatore di link o un ciclo di ricerca. Passare questi risultati a un modello linguistico è un'operazione più complessa di quanto sembri, perché i risultati di ricerca sono testo non attendibile e possono contenere istruzioni proprie. Questo aspetto viene analizzato in dettaglio in indirizzare un agente AI alla propria istanza SearXNG.

Questo è importante per qualsiasi applicazione basata su agenti. Un modello linguistico ha un limite temporale nei dati di addestramento, quindi deve eseguire ricerche live 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 già disponibile e le query non lasciano mai l'infrastruttura. Se si collegano 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 dell'API valgono due regole. Mantenere privata l'istanza: associare il lato API all'indirizzo di loopback o a una rete privata e consentire l'accesso soltanto ai propri host. Quindi, eseguire le query con moderazione. SearXNG inoltra le richieste ai motori di ricerca reali, quindi uno script che esegue cento query al secondo sta chiedendo a Google di bloccare il server.

Il limiter e cosa cambia per un'istanza pubblica

Il limiter è il sistema di difesa dai bot di SearXNG. Monitora gli header delle richieste, gli indirizzi e la frequenza delle richieste, quindi scarta il traffico che sembra automatizzato. Per mantenere questo stato usa Valkey, incluso per questo nel file Compose.

In un'istanza privata mantieni limiter: false. I tuoi script generano 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 location di nginx, l'autenticazione HTTP di base oppure un firewall che consenta l'accesso soltanto agli altri 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, abilita entrambi gli switch.

server:
  limiter: true
  public_instance: true

Un controllo più dettagliato è disponibile 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; altrimenti il limiter considera l'indirizzo di nginx come quello dell'unico client responsabile dell'abuso.

[botdetection]
trusted_proxies = [
  '127.0.0.0/8',
  '::1',
]

[botdetection.ip_limit]
link_token = true

link_token = true fa sì che SearXNG emetta un token che solo una sessione reale del browser può recuperare, bloccando la maggior parte degli scraper più semplici. Un'istanza pubblica inizierà ad attirare scraper entro pochi giorni. Sono probabili anche errori dei motori, perché più traffico inoltri, più rapidamente i motori upstream inizieranno a restituire CAPTCHA all'indirizzo del tuo 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 self-hostare nel 2026. Inoltre, non tutte le voci di questi elenchi sono infrastruttura: ricostruire una libreria Jellyfin come un videonoleggio percorribile degli anni 90 significa usare lo stesso container dietro lo stesso blocco nginx, orientato a una serata invece che a un flusso di lavoro.

Perché le ricerche non restituiscono risultati

Apri /stats nella tua istanza. Elenca tutti i motori con il relativo tasso di errore e tempo di risposta. È il primo punto da controllare quando i risultati sembrano insufficienti.

Un motore che mostra errori "Access denied" o "CAPTCHA" ha bloccato l'indirizzo del server. È un caso comune per gli indirizzi appartenenti a intervalli usati dai data center, perché i motori di ricerca presumono che siano utilizzati da scraper. SearXNG sospende quindi il motore che genera errori per un certo periodo, invece di riprovare. Di conseguenza, un motore bloccato scompare dai risultati senza segnalarlo chiaramente. Disabilitalo in settings.yml oppure accetta la perdita. Queste non sono le uniche opzioni, perché alcuni blocchi CAPTCHA possono essere risolti in modo persistente anche dopo un riavvio. Gli altri motori continuano a rispondere. Un errore 429 è il caso ambiguo, perché può essere causato dal tuo limitatore oppure da un motore upstream che rifiuta il server. Prima di modificare le impostazioni, la riga del log indica quale delle due situazioni si sta verificando.

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 ok

Il sistema non ti segnalerà quando questo controllo inizia a fallire. Eseguilo quindi tramite cron e fai in modo che un errore inviia un avviso 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 che interroga, perché questi vedono il server effettuare la richiesta invece del browser. Non nasconde la query al server e non nasconde il server ai motori. In un'istanza per un singolo utente, tutto il traffico proveniente da quell'indirizzo è il tuo, quindi l'indirizzo stesso diventa l'identificatore. Il traffico tra il browser e l'istanza è protetto dal certificato TLS. Le conseguenze rispetto al tuo ISP, al gestore di un'istanza pubblica e ai motori stessi sono illustrate in cosa nasconde realmente SearXNG.

Perché una richiesta JSON restituisce 403 Forbidden?

Le cause sono due ed entrambe dipendono dalla configurazione. json può mancare dall'elenco formats nella sezione search: di settings.yml, che è lo stato predefinito, oppure il limiter può essere attivo e avere 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 dal reverse proxy.

Mi serve il container Valkey se tengo disattivato il limiter?

Lascialo in esecuzione. SearXNG funziona anche senza, ma senza il container non puoi attivare successivamente il limiter e inoltre il container conserva altro stato temporaneo. È di dimensioni ridotte e memorizza soltanto dati nella cache, quindi rimuoverlo consente di risparmiare molto poco e fa perdere questa possibilità.

Come aggiorno SearXNG?

Esegui docker compose pull e poi docker compose up -d in /opt/searxng. Compose ricrea i container la cui immagine è cambiata e lascia invariata la directory core-config/, quindi settings.yml rimane disponibile. Poiché use_default_settings: true unisce le tue chiavi ai valori predefiniti forniti, le opzioni aggiunte upstream ricevono valori sensati invece di rendere il file non valido.

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 è necessario gestire account. Monitora /stats per una settimana dopo averla resa accessibile, perché i motori upstream iniziano a rifiutare le richieste del server molto prima che tu noti la mancanza di risultati.