Ollama : sécuriser une API sans mot de passe
Ollama n’active aucune authentification : toute machine joignant le port 11434 peut exécuter vos modèles, en télécharger et les supprimer. Voici 3 correctifs.
L’API Ollama n’a pas de mot de passe
L’API Ollama ne propose aucune authentification. Le serveur que vous exécutez ne vérifie ni utilisateur, ni mot de passe, ni clé, et n’applique aucune allowlist. Toute machine capable d’ouvrir une connexion TCP vers le port 11434 peut lister vos modèles, les exécuter, en télécharger de nouveaux et supprimer ceux que vous possédez.
La documentation officielle l’indique clairement : « Aucune authentification n’est requise pour accéder localement à l’API Ollama via http://localhost:11434. » Le terme localement définit à lui seul le modèle de sécurité. Ollama se lie par défaut à 127.0.0.1 ; sur un laptop, l’interface loopback sert donc de contrôle d’accès. Si vous déplacez ce listener vers une adresse publique, le contrôle d’accès disparaît, car rien ne le remplace.
C’est pourquoi ce point est important sur un VPS (virtual private server). La configuration par défaut est sûre. La première modification effectuée par la plupart des utilisateurs consiste à ouvrir le listener pour qu’une seconde machine puisse utiliser le modèle. C’est précisément cette modification qui supprime toutes les protections en une seule fois.
Ce que révèle le port ouvert 11434
Tous les endpoints sont accessibles. Il n’existe ni mode en lecture seule ni port d’administration distinct. Il s’agit des véritables requêtes, envoyées à l’adresse du serveur au lieu de localhost :
# List every model on the box
curl http://SERVER_IP:11434/api/tags
# See what is loaded into memory right now
curl http://SERVER_IP:11434/api/ps
# Run a prompt on your hardware
curl http://SERVER_IP:11434/api/generate -d '{"model":"llama3.2","prompt":"Why is the sky blue?"}'
# Write several gigabytes to your disk
curl http://SERVER_IP:11434/api/pull -d '{"model":"llama3.2"}'
# Remove a model
curl -X DELETE http://SERVER_IP:11434/api/delete -d '{"model":"llama3.2"}'Du point de vue de l’exploitation, quatre problèmes se posent :
- Votre CPU ou votre GPU exécute des inférences pour quelqu’un d’autre. Avec une offre qui applique une limite d’utilisation équitable du CPU, une charge soutenue consomme votre quota au profit d’un inconnu. Maîtriser les coûts des charges IA sur un VPS devient beaucoup plus difficile dès lors que vous n’êtes plus le seul appelant.
/api/pullécrit sur votre disque. Chaque modèle occupe entre deux et quarante gigaoctets. Une boucle de téléchargements remplit le volume, et un disque plein interrompt tous les autres services du serveur, pas seulement Ollama.- Les requêtes arrivent dans votre processus et sont journalisées. Avec le niveau de journalisation par défaut, Ollama enregistre uniquement les métadonnées : endpoint, statut, latence et adresse du client, mais pas le texte du prompt. Cela constitue tout de même une trace de l’utilisateur de votre serveur et de son usage, stockée dans votre journal, sans que vous ayez choisi de la collecter.
/api/deletesupprime des modèles. Pour les récupérer, vous devez les télécharger de nouveau en utilisant votre propre bande passante.
Aucune exploitation de faille n’est nécessaire. Il s’agit de l’API documentée, qui fonctionne exactement comme prévu.
La clé Ed25519 ne contrôle pas l’accès
Recherchez « Ollama API key » et vous trouverez deux éléments différents. Aucun n’est un mot de passe pour votre serveur. Les distinguer permet d’éviter la plupart des confusions.
Le premier est la paire de clés d’identité. Ollama génère une paire de clés Ed25519 lors de son premier démarrage. Sous Linux, le script d’installation crée un utilisateur système nommé ollama, dont le répertoire personnel est /usr/share/ollama. La paire se trouve donc ici :
/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pubCette clé sert à s’authentifier auprès d’un service externe. ollama signin enregistre la partie publique dans votre compte ollama.com. Elle vous autorise à envoyer un modèle dans le registre ou à récupérer un modèle privé. Elle prouve l’identité de votre machine auprès d’ollama.com. Elle ne demande rien aux clients qui se connectent à votre machine. La supprimer, la renouveler ou ne jamais la créer ne change rien aux personnes autorisées à appeler votre API.
Le second est OLLAMA_API_KEY. Cette variable contient une clé que vous créez à https://ollama.com/settings/keys. Votre client l’envoie comme Authorization: Bearer $OLLAMA_API_KEY lorsqu’il appelle l’API hébergée à https://ollama.com/api. Il s’agit d’un identifiant d’authentification pour leur service, que vous utilisez en tant que client. Votre propre ollama serve ne la lit jamais. Définir OLLAMA_API_KEY sur votre VPS ne protège pas votre VPS par un mot de passe.
Il n’y a donc aucun paramètre à activer. Les trois mécanismes de protection ci-dessous fonctionnent de la même manière : rendre le port inaccessible et placer devant lui un composant qui vérifie bien les accès.
Vérifiez sur quelles adresses votre serveur écoute actuellement
sudo ss -tlnp | grep 11434Le résultat sûr indique l’adresse de loopback :
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))Le résultat exposé indique toutes les interfaces :
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))0.0.0.0 désigne toutes les adresses IPv4 de la machine, y compris l’adresse publique. *:11434 et [::]:11434 désignent la même chose avec IPv6.
Vérifiez maintenant depuis l’extérieur. Exécutez cette commande sur votre ordinateur portable, pas sur le serveur :
curl -m 5 http://YOUR_SERVER_IP:11434/api/versioncurl: (28) Connection timed out after 5001 milliseconds est le résultat attendu, tout comme curl: (7) Failed to connect ... Connection refused. Un objet JSON contenant un champ version signifie que toute l’API est accessible à quiconque en fait la demande. Tester avec curl directement sur le serveur ne prouve rien, car la loopback répond toujours.
L’exposition provient généralement de l’une de ces deux situations. La première est une modification volontaire de la configuration, parce qu’une autre machine devait accéder au modèle :
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"Cette seule ligne suffit à exposer le service. L’autre situation concerne Docker et ne nécessite aucune modification manuelle. Elle fait l’objet de la section suivante.
Défense 1 : le laisser sur localhost et utiliser un tunnel
Commencez par cette solution. Elle ne nécessite aucun nouveau logiciel et ne crée aucun identifiant susceptible de fuiter. Le port n’existe jamais sur une interface publique. Les scans ne peuvent donc pas le trouver.
Définissez explicitement l’adresse d’écoute au lieu de vous fier à la valeur par défaut :
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"Cette commande écrit /etc/systemd/system/ollama.service.d/override.conf. Appliquez la modification et vérifiez-la :
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434ss doit maintenant afficher 127.0.0.1:11434. Si 0.0.0.0 apparaît encore, un second fichier drop-in prend le dessus. Exécutez systemctl cat ollama.service pour lister l’unité et tous les fichiers drop-in avec leur chemin, puis supprimez celui qui est obsolète.
Pour utiliser le modèle depuis votre laptop, transférez le port avec SSH :
ssh -N -L 11434:127.0.0.1:11434 you@your-server-L 11434:127.0.0.1:11434 ouvre le port 11434 sur votre laptop et envoie tout ce qui y arrive vers 127.0.0.1:11434, tel que le serveur le voit. -N indique à SSH de ne pas exécuter de commande distante. Le processus maintient donc simplement le tunnel ouvert. Tant que le tunnel fonctionne, cette commande fonctionne sur votre laptop :
curl -s http://localhost:11434/api/tagsVous rencontrerez deux problèmes. bind [127.0.0.1]:11434: Address already in use signifie qu’Ollama fonctionne déjà sur ce port sur votre laptop. Choisissez donc un autre port local avec -L 11500:127.0.0.1:11434 et configurez votre client pour utiliser le port 11500. Une réponse vide via un tunnel qui s’est correctement établi signifie que SSH fonctionne, mais qu’Ollama n’écoute pas côté serveur. Vérifiez donc ss sur le serveur avant de modifier la commande SSH.
Pour plusieurs machines clientes, un réseau privé est préférable à un tunnel par personne. Connectez les machines à WireGuard ou Tailscale, puis liez Ollama à son adresse sur ce réseau plutôt qu’à 0.0.0.0 :
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"Le port n’existe alors que sur une interface dont l’accès nécessite une clé. Cette configuration résiste également à une erreur de firewall : même une règle qui autorise accidentellement tout le monde ne peut pas exposer un service d’écoute que l’interface publique ne possède pas.
Défense 2 : un reverse proxy qui vérifie un bearer token
Lorsqu’un élément de l’Internet public doit appeler le modèle, gardez Ollama sur la loopback et placez un proxy devant lui. Le proxy termine TLS (transport layer security) et rejette les requêtes sans le bon en-tête. Ollama accepte toujours les connexions uniquement depuis 127.0.0.1, le proxy est donc le seul point d’accès.
Générez d’abord un vrai token. N’en inventez pas un manuellement :
openssl rand -base64 36Un site nginx qui le vérifie :
map $http_authorization $ollama_ok {
default 0;
"Bearer PASTE_YOUR_GENERATED_TOKEN_HERE" 1;
}
server {
listen 443 ssl;
server_name llm.example.com;
ssl_certificate /etc/letsencrypt/live/llm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/llm.example.com/privkey.pem;
location = /api/pull { return 403; }
location = /api/delete { return 403; }
location = /api/push { return 403; }
location / {
if ($ollama_ok = 0) { return 401; }
proxy_pass http://127.0.0.1:11434;
proxy_set_header Host 127.0.0.1:11434;
proxy_buffering off;
proxy_read_timeout 600s;
}
}Cinq lignes effectuent ici un vrai travail. Chacune empêche une erreur que vous rencontreriez autrement.
if dans un bloc location est généralement une mauvaise idée dans nginx. Cependant, un corps de requête exactement égal à return fait partie des deux formes qui se comportent de manière prévisible. Cette utilisation est donc sûre.
location = /api/pull est une correspondance exacte. nginx donne la priorité aux correspondances exactes sur le préfixe location /. Ces trois endpoints sont donc refusés avant même la vérification du token. Un token valide autorise alors l’inférence, mais pas le remplissage du disque.
proxy_set_header Host 127.0.0.1:11434; est important, car Ollama examine les en-têtes Host et Origin entrants. Transmettre directement le nom d’hôte public du proxy peut produire un 403 Forbidden provenant d’Ollama plutôt que de nginx, ce qui complique le diagnostic. OLLAMA_ORIGINS est l’autre paramètre important pour un client navigateur qui doit avoir une origine précise autorisée.
proxy_buffering off; est important, car Ollama diffuse sa réponse token par token. Lorsque la mise en mémoire tampon est activée, nginx conserve le flux et le transmet en une seule fois à la fin. Votre client semble alors bloqué pendant toute la génération.
proxy_read_timeout 600s; est important, car nginx utilise par défaut 60 secondes. Une longue génération sur CPU dépasse facilement cette durée. Le client reçoit 504 Gateway Time-out et /var/log/nginx/error.log enregistre upstream timed out (110: Connection timed out) while reading response header from upstream. La requête continuait pourtant de fonctionner. nginx a simplement abandonné.
Rechargez la configuration et testez les deux chemins :
sudo nginx -t && sudo systemctl reload nginx
curl -s -o /dev/null -w '%{http_code}\n' https://llm.example.com/api/tags
curl -s -H "Authorization: Bearer YOUR_TOKEN" https://llm.example.com/api/tagsLe premier doit afficher 401. Le second doit afficher la liste de vos modèles. Si le premier renvoie également la liste des modèles, le bloc map se trouve dans le mauvais contexte. Il doit être placé au niveau http. Mettez-le donc dans un fichier sous /etc/nginx/conf.d/ ou au-dessus du bloc server, jamais à l’intérieur de server.
Caddy fait la même chose avec une authentification basique en quatre lignes. Cette solution convient mieux à un client navigateur qu’à un bearer token :
llm.example.com {
basic_auth {
apiuser PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:11434
}Exécutez caddy hash-password pour produire le hash bcrypt attendu. Attention à un changement de nom : la directive s’appelait basicauth avant Caddy v2.8 et s’appelle maintenant basic_auth. Une configuration copiée depuis un ancien guide refuse donc de se charger, et Caddy indique le nom de la directive qu’il ne reconnaît pas.
Quel que soit le proxy choisi, il s’agit d’un secret partagé unique pour tous les utilisateurs. Chaque client qui le détient dispose des mêmes droits. Pour le révoquer, vous devez modifier la configuration et mettre à jour tous les appelants en même temps.
Défense 3 : une gateway qui délivre des clés par client
Dès que plusieurs personnes ou applications appellent le modèle, un token partagé atteint vite ses limites. Vous ne pouvez pas déterminer quel client est à l’origine de la charge ni en isoler un sans les isoler tous. Une gateway prend la place du proxy, utilise la même API compatible avec OpenAI, délivre une clé distincte à chaque client et enregistre l’utilisation associée à chaque clé. Une gateway LiteLLM auto-hébergée est généralement la solution retenue. Elle ajoute des budgets par clé et des journaux de requêtes au contrôle d’accès.
La règle de la défense 1 ne change pas. Ollama est lié à 127.0.0.1, la gateway est le seul processus à lui parler et elle est le seul service à écouter sur un port public. Une gateway installée sur une machine où le port 11434 est toujours ouvert à Internet ne sert à rien, car les clients peuvent simplement la contourner.
Le piège du firewall : un port de conteneur publié contourne UFW
C’est ainsi que des instances exposées se retrouvent sur des serveurs dont les propriétaires ont correctement configuré le firewall.
UFW (uncomplicated firewall) écrit ses règles dans la chaîne INPUT de la table filter du noyau, et INPUT traite les paquets destinés à l’hôte lui-même. L’option -p de Docker écrit une règle de destination NAT (network address translation) dans la chaîne PREROUTING de la table nat, que le noyau évalue avant de déterminer où le paquet doit aller. Lorsque la décision de routage intervient, la destination a déjà été réécrite vers l’adresse du conteneur. Le paquet est donc transféré au lieu d’être livré localement et traverse FORWARD au lieu de INPUT. Les règles INPUT d’UFW ne sont jamais consultées. Le paquet contourne donc le firewall au lieu de le traverser.
C’est pourquoi cette séquence laisse le port 11434 ouvert sur Internet :
sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollamaet sudo ufw status indique toujours que le firewall est actif avec une politique par défaut qui refuse les connexions. Ces deux résultats sont corrects en même temps. C’est précisément pour cela que l’on se fie au mauvais résultat. Vous pouvez voir la règle à l’origine du problème :
sudo iptables -t nat -L DOCKER -nLa correction consiste à ajouter une adresse dans l’option de publication :
docker rm -f ollama
docker run -d -v ollama:/root/.ollama -p 127.0.0.1:11434:11434 --name ollama ollama/ollama-p 11434:11434 est une forme abrégée de -p 0.0.0.0:11434:11434. Indiquer 127.0.0.1 lie le côté hôte du mapping à l’interface loopback. Votre tunnel SSH et votre reverse proxy peuvent donc toujours y accéder, contrairement à Internet. Recréer le conteneur ne présente aucun risque ici, car les modèles se trouvent dans le volume nommé ollama, et non dans le conteneur.
Vérifiez que les deux résultats concordent :
docker port ollama
sudo ss -tlnp | grep 11434docker port ollama doit afficher 11434/tcp -> 127.0.0.1:11434. S’il affiche 0.0.0.0:11434, le service est toujours exposé. Une fois ce mécanisme compris, il s’applique à tous les conteneurs que vous publierez : pourquoi les ports publiés par Docker contournent UFW explique la chaîne DOCKER-USER et les règles qui persistent après un redémarrage de Docker. Si vous êtes encore en train de définir la politique du serveur lui-même, les règles UFW nécessaires sur un nouveau VPS présente la base sur laquelle cette configuration repose. Sur Rocky ou AlmaLinux, il n’y a pas d’UFW à configurer. Commencez donc par la même politique de base avec firewalld.
Quel compte exécute le processus
Le script d’installation Linux crée un compte dédié et exécute le service avec ce compte :
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaL’unité située dans /etc/systemd/system/ollama.service définit ensuite User=ollama et Group=ollama. Ne modifiez pas ces paramètres. Un ollama serve lancé manuellement dans un terminal s’exécute avec le compte de la session ouverte. Si ce compte est root, une API non authentifiée écrit alors des fichiers en tant que root. Vérifiez le compte utilisé :
ps -o user= -C ollamaLa réponse doit être ollama. Toute autre réponse signifie qu’un processus lancé manuellement s’exécute en parallèle de l’unité ou à sa place. Le même principe s’applique à chaque daemon que vous ajouterez ensuite, et la exécution des services avec des comptes disposant des privilèges minimaux permet de le faire correctement.
Vérifier que le endpoint de l’API Ollama est sécurisé
Quel que soit votre choix, un test permet de trancher. Il doit être exécuté depuis une autre machine :
curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tagsLes deux requêtes doivent expirer ou être refusées. Si vous avez mis en place un proxy, les deux mêmes chemins sur le hostname du proxy doivent renvoyer 401 sans identifiants, et un vrai JSON avec des identifiants.
Consultez ensuite une fois l’access log. Il indique si quelqu’un a trouvé le port pendant qu’il était ouvert :
journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1Ollama écrit une ligne par requête et inclut l’adresse du client :
[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"Chaque ligne doit afficher 127.0.0.1 une fois qu’Ollama écoute sur loopback, car c’est la seule adresse depuis laquelle une connexion peut arriver. Une adresse publique dans cette colonne indique une requête externe, et l’horodatage indique quand elle a eu lieu. L’absence totale de sortie avec cette commande est le résultat attendu. Si l’exécution de modèles vous est encore peu familière, exécuter Ollama sur un VPS détaille l’installation, le dimensionnement du modèle et les limites de mémoire qui déterminent ce qui pourra réellement être chargé.
FAQ
Ollama dispose-t-il d’une clé API ou d’un mot de passe ?
Non. Le serveur que vous exécutez n’utilise aucune forme d’authentification, et la documentation officielle indique qu’aucune authentification n’est requise pour accéder à l’API. Les deux éléments appelés « clé API Ollama » servent à autre chose. La paire Ed25519 dans /usr/share/ollama/.ollama/ authentifie votre machine auprès de ollama.com afin que vous puissiez envoyer des modèles et récupérer des modèles privés. OLLAMA_API_KEY est un identifiant d’authentification que votre client envoie à l’API hébergée sur https://ollama.com/api. Votre propre ollama serve ne lit aucun de ces deux éléments. Le contrôle d’accès doit donc être assuré par le réseau ou par un proxy placé en amont.
OLLAMA_HOST=0.0.0.0 est-il sûr si j’ai un pare-feu ?
Uniquement tant qu’aucun autre composant n’écrit de règles de pare-feu sur cette machine. 0.0.0.0 signifie que le listener est réellement présent sur l’interface publique et que vous comptez uniquement sur le pare-feu pour empêcher son accès. Cette confiance disparaît dès que Docker publie un port, car la règle DNAT ajoutée par Docker à la table nat est évaluée avant que le paquet n’atteigne la chaîne INPUT où intervient UFW. Le paquet est donc transféré et UFW ne le voit jamais. Lier le service à 127.0.0.1 ou à l’adresse d’un tunnel privé retire le listener de l’interface publique. Une erreur de pare-feu n’a alors plus rien à exposer.
Comment vérifier si mon port Ollama est ouvert sur Internet ?
Exécutez sudo ss -tlnp | grep 11434 sur le serveur et curl -m 5 http://YOUR_SERVER_IP:11434/api/version depuis une autre machine. Si ss affiche 127.0.0.1:11434 et que la commande curl distante expire, vous obtenez le résultat attendu. Si ss affiche 0.0.0.0:11434 ou *:11434 tandis que la commande curl distante renvoie du JSON, l’API complète est accessible. Ne testez jamais avec curl sur le serveur lui-même, car loopback répond quelle que soit l’adresse d’écoute configurée.
Puis-je simplement remplacer le port 11434 par un port aléatoire ?
Non, et la raison mérite d’être précisée. Un autre port ne ralentit qu’un scan limité à un seul port. Les scanners parcourent toute la plage de ports, et une requête vers /api/tags identifie le service, quel que soit le port utilisé. Changer de port casse également les valeurs par défaut de tous les clients et rend votre configuration plus difficile à comprendre par la suite. Liez plutôt le service à loopback. Le listener est alors supprimé de l’interface publique au lieu d’être déplacé.
Quelqu’un a accédé à mon instance Ollama ouverte. Que dois-je vérifier ?
Liez-la d’abord à 127.0.0.1 et redémarrez le service, afin de supprimer l’exposition avant de commencer l’analyse. Exécutez ensuite journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 pour voir quelles adresses externes ont appelé quels endpoints et à quel moment. Comparez ollama list avec les modèles que vous prévoyiez d’avoir, car /api/pull n’utilise aucune authentification et un modèle que vous n’avez pas récupéré représente à la fois une consommation d’espace disque et un élément de preuve. Vérifiez l’espace libre avec df -h. Ollama n’enregistre pas le texte des prompts avec le niveau de journalisation par défaut. Vous disposez donc d’un relevé indiquant qui a effectué la demande et pour quel modèle, mais pas du contenu généré.