Installer SearXNG en privé avec Docker Compose
Déployez SearXNG sur votre VPS avec Docker Compose : settings.yml, limiter, nginx avec TLS et API JSON pour vos scripts, sans clé ni quota par requête.
Ce que vous allez construire
L’auto-hébergement de SearXNG vous fournit un moteur de recherche privé qui s’exécute sur votre propre serveur. SearXNG est un métamoteur de recherche : il reçoit votre requête, interroge d’autres moteurs comme Google, Bing, DuckDuckGo et Wikipedia, puis regroupe les résultats dans une seule page. Aucun profil n’est créé et aucun cookie de suivi n’est défini, car votre requête n’est conservée que sur votre propre machine.
La stack est réduite. Deux conteneurs, un fichier de configuration et un reverse proxy. La vraie décision consiste à choisir entre une instance privée, accessible uniquement par vous et vos propres scripts, et une instance publique, que n’importe qui sur Internet peut interroger. Ce choix modifie les paramètres de sécurité. Faites-le avant de saisir quoi que ce soit. Par défaut, choisissez une instance privée.
Il existe une autre raison d’en exécuter une. Une instance SearXNG parle JSON. Ainsi, les scripts ou les agents IA que vous écrivez disposent d’une API de recherche que vous contrôlez, sans clé, sans facturation par requête et sans e-mail concernant un quota.
Installer SearXNG avec Docker Compose
Le projet publie une image de conteneur et un fichier Compose. Téléchargez les deux sur un serveur Ubuntu 24.04 neuf qui dispose déjà de Docker Engine et du plugin Compose. Si Docker est nouveau pour vous, commencez par les bases de Docker Compose sur un VPS, puis revenez ici.
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 .envLe fichier Compose définit deux services. core est SearXNG lui-même, et valkey est un magasin de données en mémoire utilisé pour la limitation du débit et pour l’état à courte durée de vie. Il monte ./core-config/ dans /etc/searxng/ à l’intérieur du conteneur. Toute votre configuration se trouve donc dans ce répertoire sur l’hôte.
Modifiez maintenant .env. Toutes les lignes de l’exemple fourni sont commentées. C’est pourquoi le conteneur démarre sur le port 8080, sur toutes les adresses. Décommentez et définissez ces trois lignes.
SEARXNG_VERSION=latest
SEARXNG_HOST=127.0.0.1
SEARXNG_PORT=8080SEARXNG_HOST=127.0.0.1 est le paramètre important. Il publie le port 127.0.0.1:8080:8080 au lieu de [::]:8080:8080. Le conteneur répond ainsi uniquement sur l’adresse loopback, et Internet ne peut pas y accéder directement. Si vous omettez ce paramètre, le conteneur est exposé dès son démarrage, car un port Docker publié est inséré avant vos règles de pare-feu. Ce piège mérite une lecture complète : les ports Docker publiés contournent ufw.
SEARXNG_VERSION=latest convient pendant votre apprentissage. Sur un serveur important, épinglez le tag. En juillet 2026, les tags de release sont basés sur la date et ressemblent à 2026.3.25-541c6c3cb. Un déploiement avec un tag épinglé est donc mis à niveau quand vous le décidez, et non lorsque le registre est modifié.
settings.yml : les éléments importants
Créez core-config/settings.yml avant le premier démarrage. use_default_settings: true indique à SearXNG de charger ses valeurs par défaut intégrées, puis d’appliquer uniquement les clés que vous avez écrites. Votre fichier reste ainsi court et résiste aux mises à niveau qui ajoutent de nouvelles options.
Générez d’abord le secret, car sa valeur sera directement inscrite dans le fichier.
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 signe les données de session et de token. La valeur par défaut fournie est la chaîne littérale ultrasecretkey. Si vous la conservez, toute personne qui la connaît peut falsifier ces tokens. Remplacez-la une fois, puis ne la modifiez plus : la changer supprime toutes les préférences enregistrées.
base_url doit être l’adresse HTTPS publique, avec la barre oblique finale. SearXNG l’utilise pour générer les liens qu’il affiche. Si cette valeur pointe vers localhost, le lien « page suivante » ouvert dans un navigateur distant pointe vers la machine du lecteur et échoue.
formats détermine les types de sortie que l’endpoint web produira. json ne figure pas dans la liste par défaut. Une requête JSON renvoie donc une erreur 403 tant que vous ne l’ajoutez pas. image_proxy: true fait transiter les miniatures des résultats par votre serveur. Les sites qui hébergent ces images ne voient ainsi jamais les adresses de vos visiteurs.
Le valkey.url utilise le nom d’hôte valkey, car il s’agit du nom du service dans le fichier Compose. Compose place les deux conteneurs sur un même réseau, où les noms de service sont résolus. Si vous le faites pointer vers localhost, le limiter échoue, car localhost désigne ce conteneur à l’intérieur du conteneur core.
Le secret se trouve dans un fichier en clair. Protégez donc le répertoire qui le contient plutôt que le fichier lui-même. chmod 750 /opt/searxng empêche les autres utilisateurs de l’hôte d’y accéder. Ne réduisez pas core-config/settings.yml au mode 600 : le conteneur s’exécute avec son propre utilisateur non privilégié, et un fichier qu’il ne peut pas lire empêche complètement SearXNG de démarrer.
Démarrez la stack et vérifiez son état.
cd /opt/searxng
docker compose up -d
docker compose ps
curl -I http://127.0.0.1:8080/docker compose ps doit afficher les deux conteneurs avec l’état running. Le curl doit répondre avec HTTP/1.1 200 OK. S’il ne renvoie rien, consultez docker compose logs core : une erreur YAML dans settings.yml y apparaît sous la forme d’une erreur d’analyse indiquant la ligne concernée.
Placez-le derrière nginx avec TLS
Le conteneur écoute uniquement sur loopback. nginx le rend donc accessible et ajoute également la sécurité de la couche transport (TLS). Écrivez /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 affiche syntax is ok et test is successful avant le rechargement. Certbot réécrit le même fichier pour écouter sur 443 avec un certificat et ajoute une redirection depuis le port 80. L’enregistrement DNS de search.example.com doit déjà pointer vers ce serveur, car l’autorité de certification prouve la propriété du domaine en récupérant un fichier via HTTP. La procédure complète, renouvellement compris, se trouve dans le guide Certbot et nginx pour Ubuntu 24.04.
Les deux en-têtes de transmission ne sont pas décoratifs. Sans X-Forwarded-For et X-Real-IP, chaque requête arrivant à SearXNG contient l’adresse du proxy. Le limiteur de débit voit donc un seul client générer tout le trafic et ne peut pas distinguer les visiteurs.
Pourquoi les scripts et les agents veulent une API de recherche JSON
Avec json dans formats, le même endpoint qui affiche la page renvoie des données structurées.
curl -s 'http://127.0.0.1:8080/search?q=wireguard+mtu&format=json' \
| jq -r '.results[0:5][] | .url'Vous récupérez un objet contenant un tableau results. Chaque entrée contient url, title, content et le moteur qui l’a fournie, ainsi que answers, infoboxes et suggestions. Cela suffit pour alimenter un outil de résumé, un vérificateur de liens ou une boucle de recherche.
Cela est important pour tout ce qui a une dimension d’agent. Un modèle de langage a une date de coupure pour ses données d’entraînement. Il a donc besoin d’une recherche en temps réel pour répondre aux questions sur l’actualité. Les API de recherche commerciales facturent chaque requête et appliquent des limitations de débit strictes. Une instance locale ne nécessite qu’un conteneur sur un serveur que vous payez déjà, et les requêtes ne quittent jamais ce serveur. Si vous connectez des outils à un modèle, le même raisonnement s’applique à l’exécution de serveurs MCP sur un VPS, où un outil de recherche est généralement le premier que l’on ajoute.
Deux règles s’appliquent à l’utilisation de l’API. Gardez l’instance privée. Liez donc l’API à l’adresse loopback ou à un réseau privé, et n’autorisez que vos propres hôtes à y accéder. Interrogez-la ensuite avec modération. SearXNG transmet votre requête à de vrais moteurs de recherche. Un script qui exécute cent requêtes par seconde demande donc à Google de bloquer votre serveur.
Le limiteur et les changements pour une instance publique
Le limiteur assure la protection de SearXNG contre les bots. Il surveille les en-têtes des requêtes, les adresses et le débit des requêtes. Il bloque le trafic qui semble automatisé. Il a besoin de Valkey pour stocker cet état. C'est pourquoi le fichier Compose l'inclut.
Sur une instance privée, conservez limiter: false. Vos propres scripts génèrent par définition du trafic automatisé. Le limiteur bloquerait donc précisément les appels JSON pour lesquels vous avez créé l'instance. Le contrôle d'accès relève plutôt du reverse proxy : une paire allow et deny dans le fichier de configuration nginx location, l'authentification HTTP Basic ou un firewall qui n'autorise que vos autres serveurs.
Si vous publiez l'instance pour d'autres utilisateurs, activez les deux options.
server:
limiter: true
public_instance: trueUn contrôle plus précis se configure dans core-config/limiter.toml, que le conteneur lit à l'emplacement /etc/searxng/limiter.toml. Vous n'y écrivez que les clés que vous voulez modifier. Derrière un proxy, vous devez déclarer celui-ci. Sinon, le limiteur considère l'adresse de votre nginx comme celle du client abusif.
[botdetection]
trusted_proxies = [
'127.0.0.0/8',
'::1',
]
[botdetection.ip_limit]
link_token = truelink_token = true demande à SearXNG d'émettre un token que seule une session de navigateur réelle récupérera. Cela bloque la plupart des scrapers simples. Une instance publique attirera probablement ces scrapers en quelques jours. Attendez-vous également à des erreurs de moteur. Plus vous relayez de trafic, plus les moteurs en amont commenceront rapidement à envoyer des CAPTCHA à l'adresse de votre serveur. Une instance SearXNG publique demande une maintenance continue. Une instance privée ne présente pas cette contrainte. C'est pourquoi elle figure dans la plupart des listes courtes de services qui valent la peine d'être auto-hébergés en 2026.
Pourquoi les recherches ne renvoient aucun résultat
Ouvrez /stats sur votre instance. Cette page répertorie chaque moteur avec son taux d’erreur et son temps de réponse. C’est le premier endroit à consulter lorsque les résultats sont peu nombreux.
Un moteur qui affiche des erreurs « Access denied » ou « CAPTCHA » a bloqué l’adresse de votre serveur. Cela arrive souvent avec les adresses appartenant à des plages de data centers, car les moteurs de recherche supposent qu’elles sont utilisées par des scrapers. SearXNG suspend alors le moteur défaillant pendant un certain temps au lieu de réessayer. Un moteur bloqué disparaît donc discrètement de vos résultats. Désactivez-le dans settings.yml ou acceptez cette perte. Les autres moteurs continuent de répondre.
Si tous les moteurs échouent en même temps, le container ne dispose d’aucune résolution de noms sortante fonctionnelle ou d’aucune route vers Internet. Testez ces éléments depuis l’intérieur du container.
docker compose exec core wget -qO- https://duckduckgo.com > /dev/null && echo okFAQ
SearXNG rend-il mes recherches anonymes ?
Il masque votre identité aux moteurs qu’il interroge, car ceux-ci voient votre serveur effectuer la requête au lieu de voir votre navigateur. Il ne masque pas la requête à votre serveur et ne masque pas votre serveur aux moteurs. Sur une instance utilisée par une seule personne, tout le trafic provenant de cette adresse vous appartient. L’adresse devient donc elle-même l’identifiant. Le trafic entre votre navigateur et votre instance est protégé par le certificat TLS.
Pourquoi une requête JSON renvoie-t-elle 403 Forbidden ?
Deux causes sont possibles, et elles concernent toutes deux la configuration. Soit json est absent de la liste formats sous search: dans settings.yml, ce qui est l’état par défaut, soit le limiter est activé et a identifié votre script comme un bot. Ajoutez d’abord le format, redémarrez avec docker compose restart core, puis réessayez. Si l’échec persiste, définissez limiter: false et contrôlez l’accès au niveau du reverse proxy.
Ai-je besoin du conteneur Valkey si je laisse le limiter désactivé ?
Laissez-le fonctionner. SearXNG fonctionne sans lui, mais le limiter ne pourra pas être activé ultérieurement sans ce conteneur. Celui-ci conserve également d’autres états à courte durée de vie. Le conteneur est peu volumineux et ne stocke que des données en cache. Sa suppression économise donc très peu d’espace et vous prive de cette possibilité.
Comment mettre SearXNG à jour ?
Exécutez docker compose pull, puis docker compose up -d dans /opt/searxng. Compose recrée chaque conteneur dont l’image a changé et laisse votre répertoire core-config/ intact. settings.yml est donc conservé. Comme use_default_settings: true fusionne vos clés avec les valeurs par défaut fournies, les options ajoutées en amont reçoivent des valeurs cohérentes au lieu de rendre le fichier invalide.
Plusieurs personnes peuvent-elles partager une même instance ?
Oui. C’est dans ce cas que vous activez le limiter et définissez public_instance: true. Les préférences sont stockées dans le navigateur de chaque visiteur. Vous n’avez donc aucun compte à gérer. Surveillez /stats pendant une semaine après avoir ouvert l’instance, car les moteurs en amont commencent à refuser les requêtes de votre serveur bien avant que vous ne remarquiez l’absence de résultats.