SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor

Ollama API beveiligen: zo stelt u authenticatie in

De Ollama API op poort 11434 heeft standaard geen wachtwoord. Iedereen met netwerktoegang kan uw modellen beheren. Volg deze drie concrete stappen om uw server veilig af te schermen.

De Ollama API heeft geen wachtwoord

De Ollama API bevat geen authenticatie. Er is geen gebruiker, geen wachtwoord, geen sleutelcontrole en geen toegestane lijst (allowlist) aanwezig op de server die u draait. Alles wat een TCP-verbinding kan openen naar poort 11434 kan uw modellen weergeven, uitvoeren, nieuwe modellen downloaden en de bestaande modellen verwijderen.

De officiële documentatie stelt dit duidelijk: "Er is geen authenticatie vereist bij het lokaal benaderen van de Ollama API via http://localhost:11434." Het woord lokaal vormt het gehele beveiligingsmodel. Ollama bindt standaard aan 127.0.0.1, waardoor op een laptop de loopback-interface de toegangscontrole vormt. Verplaatst u deze listener naar een publiek adres, dan vervalt de toegangscontrole omdat er niets is dat deze vervangt.

Dit is waarom dit van belang is op een VPS (virtual private server). De standaardinstelling is veilig. De eerste wijziging die de meeste mensen doorvoeren, het openstellen van de listener zodat een tweede machine het model kan gebruiken, is de wijziging die direct alle beveiliging verwijdert.

Wat een open poort 11434 prijsgeeft

Elk endpoint. Er is geen alleen-lezenmodus en geen afzonderlijke beheerpoort. Dit zijn de daadwerkelijke verzoeken, gericht aan een serveradres in plaats van 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"}'

In termen van beheer gaan er vier zaken mis:

  • Uw CPU of GPU voert inferentie uit voor iemand anders. Bij een abonnement met een fair-use CPU-limiet wordt uw toegestane capaciteit verbruikt door een vreemde, en het beheersen van AI-werklastkosten op een VPS wordt aanzienlijk lastiger zodra u niet de enige gebruiker bent.
  • /api/pull schrijft naar uw schijf. Modellen variëren in grootte van twee tot veertig gigabyte per stuk. Een lus van pulls vult het volume, en een volle schijf verstoort elke andere service op de server, niet alleen Ollama.
  • Verzoeken komen binnen in uw proces en worden gelogd. Op het standaard logniveau registreert Ollama alleen metadata; u krijgt dus het endpoint, de status, de latentie en het clientadres, niet de prompttekst. Dat is nog steeds een overzicht van wie uw server heeft gebruikt en waarvoor, opgeslagen in uw journal, terwijl u er niet voor heeft gekozen om dit te verzamelen.
  • /api/delete verwijdert modellen. Deze terugkrijgen betekent ze opnieuw downloaden via uw eigen bandbreedte.

Hiervoor is geen exploit nodig. Het is de gedocumenteerde API die zich precies gedraagt zoals ontworpen.

De Ed25519-sleutel is geen toegangscontrole

Zoek naar "Ollama API key" en u komt bij twee verschillende zaken uit. Geen van beide is een wachtwoord voor uw server, en het onderscheid hiertussen neemt de meeste verwarring weg.

De eerste is het identiteitssleutelpaar. Ollama genereert bij de eerste uitvoering een Ed25519-sleutelpaar. Op Linux maakt het installatiescript een systeemgebruiker aan genaamd ollama met de thuismap op /usr/share/ollama, dus het paar bevindt zich hier:

/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pub

Die sleutel wijst naar buiten. ollama signin registreert de publieke helft bij uw ollama.com-account, en dit is wat u autoriseert om een model naar de registry te pushen of een privaat model te pullen. Het bewijst aan ollama.com dat u uw machine bent. Het vraagt niets van de clients die verbinding maken met uw machine. Het verwijderen, roteren of nooit aanmaken ervan verandert niets aan wie uw API mag aanroepen.

De tweede is OLLAMA_API_KEY. Die variabele bevat een sleutel die u aanmaakt op https://ollama.com/settings/keys, en uw client verstuurt deze als Authorization: Bearer $OLLAMA_API_KEY bij het aanroepen van de gehoste API op https://ollama.com/api. Het is een inloggegeven voor hun dienst, door u gebruikt als de client. Uw eigen ollama serve leest deze nooit. Het instellen van OLLAMA_API_KEY op uw VPS plaatst geen wachtwoord op uw VPS.

Er is dus geen instelling om in te schakelen. De drie onderstaande verdedigingslinies werken allemaal op dezelfde manier: houd de poort onbereikbaar en plaats er iets voor dat wel controleert.

Controleer waarop uw server momenteel luistert

sudo ss -tlnp | grep 11434

Het veilige resultaat benoemt het loopback-adres:

LISTEN 0  4096  127.0.0.1:11434  0.0.0.0:*  users:(("ollama",pid=812,fd=3))

Het blootgestelde resultaat benoemt elke interface:

LISTEN 0  4096  0.0.0.0:11434  0.0.0.0:*  users:(("ollama",pid=812,fd=3))

0.0.0.0 staat voor alle IPv4-adressen op de machine, inclusief het publieke adres. *:11434 en [::]:11434 betekenen hetzelfde, inclusief IPv6.

Bevestig dit nu vanaf een externe locatie. Voer dit uit op uw laptop, niet op de server:

curl -m 5 http://YOUR_SERVER_IP:11434/api/version

curl: (28) Connection timed out after 5001 milliseconds is het gewenste antwoord, evenals curl: (7) Failed to connect ... Connection refused. Een JSON-object met een version-veld betekent dat de volledige API bereikbaar is voor iedereen die erom vraagt. Testen met curl op de server zelf bewijst niets, omdat de loopback-interface altijd antwoordt.

Blootstelling ontstaat meestal op twee manieren. De eerste is een bewuste wijziging, omdat iemand een tweede machine nodig had om het model te bereiken:

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

Die enkele regel is de volledige blootstelling. De tweede manier is Docker, en daarbij hoeft u helemaal niets aan te passen. Dat onderwerp heeft hieronder een eigen sectie.

Verdediging 1: houd het op localhost en gebruik een tunnel

Kies hier als eerste voor. Er is geen extra software nodig en er worden geen inloggegevens aangemaakt die kunnen lekken. De poort bestaat niet op een publieke interface, waardoor deze niet vindbaar is via een scan.

Stel het bind-adres expliciet in in plaats van te vertrouwen op de standaardinstelling:

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"

Dit schrijft /etc/systemd/system/ollama.service.d/override.conf. Pas dit toe en controleer het:

sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434

ss zou nu 127.0.0.1:11434 moeten tonen. Als het nog steeds 0.0.0.0 toont, overschrijft een tweede drop-in bestand de instelling. Voer systemctl cat ollama.service uit om de unit en elke drop-in met het bijbehorende pad weer te geven, en verwijder vervolgens het verouderde bestand.

Om het model vanaf uw laptop te gebruiken, forwardt u de poort via SSH:

ssh -N -L 11434:127.0.0.1:11434 you@your-server

-L 11434:127.0.0.1:11434 opent poort 11434 op uw laptop en stuurt al het verkeer dat daar aankomt door naar 127.0.0.1:11434, gezien vanaf de server. -N vertelt SSH om geen extern commando uit te voeren, waardoor het proces de tunnel openhoudt. Terwijl dit draait, werkt het volgende op uw laptop:

curl -s http://localhost:11434/api/tags

U zult tegen twee fouten aanlopen. bind [127.0.0.1]:11434: Address already in use betekent dat uw laptop zijn eigen Ollama op die poort draait; kies daarom een andere lokale poort met -L 11500:127.0.0.1:11434 en richt uw client op 11500. Een leeg antwoord via een tunnel die wel correct verbond, betekent dat SSH werkt maar Ollama niet luistert aan de serverzijde; controleer daarom ss op de server voordat u het SSH-commando aanpast.

Voor meerdere client-machines is een privaat netwerk beter dan één tunnel per persoon. Plaats de machines op WireGuard of Tailscale en bind Ollama vervolgens aan het adres op dat netwerk in plaats van aan 0.0.0.0:

[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"

De poort bestaat dan alleen op een interface waarvoor u een sleutel nodig heeft om verbinding te maken. Dit is ook veilig bij een fout in de firewall, omdat een regel die per ongeluk toegang verleent aan de hele wereld, nog steeds geen listener kan blootstellen die niet op de publieke interface aanwezig is.

Verdediging 2: een reverse proxy die een bearer token controleert

Wanneer een service op het publieke internet het model moet aanroepen, houdt u Ollama op loopback en plaatst u er een proxy voor. De proxy beëindigt TLS (transport layer security) en wijst verzoeken zonder de juiste header af. Ollama accepteert nog steeds alleen verbindingen vanaf 127.0.0.1, dus de proxy is de enige toegangsweg.

Genereer eerst een echt token. Verzin er zelf geen:

openssl rand -base64 36

Een nginx-site die dit controleert:

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;
    }
}

Vijf regels in dit voorbeeld verrichten het eigenlijke werk, en elk daarvan voorkomt een fout die anders zou optreden.

if binnen een location-blok is in nginx meestal een slecht idee, maar een body van exact return is een van de twee vormen die voorspelbaar reageren, dus dit gebruik is veilig.

location = /api/pull is een exacte match, en nginx rangschikt exacte matches boven het location /-prefix, waardoor die drie endpoints worden geweigerd nog voordat het token wordt gecontroleerd. Een geldig token geeft vervolgens toegang tot inferentie, niet tot de mogelijkheid om uw schijf vol te schrijven.

proxy_set_header Host 127.0.0.1:11434; is van belang omdat Ollama de inkomende Host- en Origin-headers inspecteert. Het direct doorgeven van de publieke hostnaam van de proxy kan resulteren in een 403 Forbidden die afkomstig is van Ollama in plaats van nginx, wat verwarrend is bij het debuggen. OLLAMA_ORIGINS is de andere instelling, voor een browserclient die een specifieke origin moet toestaan.

proxy_buffering off; is van belang omdat Ollama het antwoord token voor token streamt. Met buffering ingeschakeld houdt nginx de stream vast en levert deze pas aan het einde in één keer af, waardoor uw client tijdens de gehele generatie bevroren lijkt.

proxy_read_timeout 600s; is van belang omdat nginx standaard op 60 seconden staat. Een lange generatie op de CPU overschrijdt dit gemakkelijk, de client ontvangt 504 Gateway Time-out, en /var/log/nginx/error.log registreert upstream timed out (110: Connection timed out) while reading response header from upstream. Het verzoek was nog steeds actief, maar nginx heeft het afgebroken.

Herlaad en test beide paden:

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/tags

De eerste zou 401 moeten afdrukken. De tweede zou uw modellijst moeten afdrukken. Als de eerste ook de modellijst teruggeeft, staat het map-blok in de verkeerde scope. Het hoort op http-niveau, dus plaats het in een bestand onder /etc/nginx/conf.d/ of boven het server-blok, nooit binnen server.

Caddy doet hetzelfde met basisauthenticatie in vier regels, wat beter geschikt is voor een browserclient dan een bearer token:

llm.example.com {
	basic_auth {
		apiuser PASTE_BCRYPT_HASH_HERE
	}
	reverse_proxy 127.0.0.1:11434
}

Voer caddy hash-password uit om de bcrypt-hash te genereren die vereist is. Eén valkuil bij de naamgeving: de richtlijn was basicauth vóór Caddy v2.8 en is nu basic_auth, dus een configuratie die uit een oudere handleiding is gekopieerd, zal niet laden en Caddy geeft aan welke richtlijn niet werd herkend.

Welke proxy u ook kiest, dit is één gedeeld geheim voor iedereen. Elke client die dit bezit, heeft identieke toegang, en het intrekken ervan betekent dat u de configuratie moet bewerken en alle aanroepende partijen tegelijkertijd moet bijwerken.

Verdediging 3: een gateway die per client sleutels uitgeeft

Zodra meer dan één persoon of applicatie het model aanroept, schiet een gedeelde token tekort. U kunt niet achterhalen welke client de belasting veroorzaakte en u kunt er niet één afsluiten zonder ze allemaal af te sluiten. Een gateway bevindt zich op de plek waar de proxy stond, spreekt dezelfde OpenAI-compatibele API, geeft per client een aparte sleutel uit en registreert het verbruik per sleutel. Een zelfgehoste LiteLLM gateway is de gebruikelijke oplossing; deze voegt budgetten per sleutel en verzoeklogs toe bovenop de toegangscontrole.

De regel uit verdediging 1 blijft ongewijzigd. Ollama bindt aan 127.0.0.1, de gateway is het enige proces dat ermee communiceert en de gateway is de enige service met een publieke listener. Een gateway op een server waar poort 11434 nog steeds openstaat voor de wereld is slechts decoratie, omdat aanroepers deze eenvoudig kunnen omzeilen.

De firewall-valstrik: een gepubliceerde containerpoort omzeilt UFW

Dit is de reden waarom blootgestelde instanties bestaan op servers waarvan de eigenaren de firewall correct hebben geconfigureerd.

UFW (uncomplicated firewall) schrijft zijn regels naar de INPUT-chain van de filter-tabel van de kernel, en INPUT verwerkt pakketten die aan de host zelf zijn geadresseerd. De -p-vlag van Docker schrijft een destination NAT-regel (network address translation) naar de PREROUTING-chain van de nat-tabel, die de kernel evalueert voordat wordt besloten waar het pakket naartoe gaat. Tegen de tijd dat de routeringsbeslissing wordt genomen, is de bestemming al herschreven naar het adres van de container, waardoor het pakket wordt doorgestuurd in plaats van lokaal afgeleverd en het FORWARD passeert in plaats van INPUT. De INPUT-regels van UFW worden nooit geraadpleegd, dus het pakket gaat om de firewall heen in plaats van erdoorheen.

Dit is de reden waarom deze reeks poort 11434 open laat staan voor het internet:

sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

en sudo ufw status nog steeds rapporteert dat de firewall actief is met een standaard weigeringsbeleid (default deny). Beide lezingen zijn tegelijkertijd correct, wat precies de reden is waarom mensen de verkeerde vertrouwen. U kunt de regel zien die dit heeft veroorzaakt:

sudo iptables -t nat -L DOCKER -n

De oplossing is één adres in de publish-vlag:

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 is een afkorting voor -p 0.0.0.0:11434:11434. Door 127.0.0.1 op te geven, wordt de host-zijde van de mapping gebonden aan loopback, zodat uw SSH-tunnel en uw reverse proxy deze nog steeds kunnen bereiken, maar het internet niet. Het opnieuw aanmaken van de container is hier veilig omdat de modellen in de benoemde ollama-volume staan, niet in de container zelf.

Bevestig dat beide weergaven overeenstemmen:

docker port ollama
sudo ss -tlnp | grep 11434

docker port ollama zou 11434/tcp -> 127.0.0.1:11434 moeten weergeven. Als het 0.0.0.0:11434 weergeeft, bent u nog steeds blootgesteld. Begrijp het mechanisme één keer en het is van toepassing op elke container die u ooit publiceert: waarom Docker gepubliceerde poorten UFW omzeilen behandelt de DOCKER-USER-chain en de regels die een herstart van Docker overleven. Als u nog steeds het hostbeleid zelf aan het opbouwen bent, behandelt de UFW-regels die een nieuwe VPS nodig heeft de basis waarop dit rust.

Onder welk account draait het proces

Het Linux-installatiescript maakt een toegewezen account aan en voert de service daaronder uit:

useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama

De unit op /etc/systemd/system/ollama.service stelt vervolgens User=ollama en Group=ollama in. Wijzig dit niet. Een snelle ollama serve die handmatig in een terminal wordt gestart, draait onder de gebruiker waarmee u bent ingelogd. Als dit root is, schrijft een niet-geauthenticeerde API bestanden als root. Controleer welk account actief is:

ps -o user= -C ollama

Het antwoord moet ollama zijn. Iets anders betekent dat er een handmatig gestart proces naast of in plaats van de unit draait. Dezelfde denkwijze is van toepassing op elke daemon die u later toevoegt, en services draaien met minimale rechten behandelt dit op de juiste wijze.

Hoe u controleert of het Ollama API-eindpunt beveiligd is

Wat u ook kiest, één test geeft uitsluitsel, en deze moet vanaf een andere machine worden uitgevoerd:

curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tags

Beide zouden een time-out moeten geven of geweigerd moeten worden. Als u een proxy heeft gebouwd, zouden dezelfde twee paden op de hostnaam van de proxy 401 moeten retourneren zonder inloggegevens en de daadwerkelijke JSON met inloggegevens.

Lees daarna eenmaal het toegangslogboek, omdat dit u vertelt of iemand de poort heeft gevonden terwijl deze openstond:

journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1

Ollama schrijft één regel per verzoek en bevat het adres van de client:

[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"

Elke regel zou 127.0.0.1 moeten tonen zodra Ollama is gebonden aan loopback, omdat dat het enige adres is waarvan een verbinding kan binnenkomen. Een publiek adres in die kolom is een verzoek van buitenaf, en de tijdstempel vertelt u wanneer. Geen enkele uitvoer van dat commando is het resultaat dat u wilt zien. Als de modelzijde hiervan nieuw voor u is, behandelt Ollama draaien op een VPS de installatie, modelgrootte en de geheugenlimieten die bepalen wat er daadwerkelijk wordt geladen.

FAQ

Heeft Ollama een API-key of een wachtwoord?

Nee. De server die u draait, heeft geen enkele vorm van authenticatie en de officiële documentatie stelt dat er geen authenticatie vereist is om de API te bereiken. Beide zaken die "Ollama API key" worden genoemd, werken de andere kant op. Het Ed25519-paar in /usr/share/ollama/.ollama/ bewijst aan ollama.com dat het uw machine is, zodat u modellen kunt pushen en privémodellen kunt pullen. OLLAMA_API_KEY is een inloggegeven dat uw client naar de gehoste API op https://ollama.com/api stuurt. Uw eigen ollama serve leest geen van beide, dus toegangscontrole moet vanuit het netwerk of via een proxy aan de voorzijde komen.

Is OLLAMA_HOST=0.0.0.0 veilig als ik een firewall heb?

Alleen zolang er niets anders firewallregels op die machine schrijft. 0.0.0.0 betekent dat de listener daadwerkelijk op de publieke interface bestaat en u vertrouwt er enkel op dat de firewall deze onbereikbaar houdt. Dat vertrouwen verdwijnt op het moment dat Docker een poort publiceert, omdat de DNAT-regel die Docker toevoegt aan de nat-tabel wordt geëvalueerd voordat het pakket de INPUT-chain bereikt waar UFW actief is; het pakket wordt dus doorgestuurd en UFW ziet het nooit. Binden aan 127.0.0.1 of aan een privétunneladres verwijdert de listener van de publieke interface, waardoor een fout in de firewall niets meer blootstelt.

Hoe controleer ik of mijn Ollama-poort openstaat voor het internet?

Voer sudo ss -tlnp | grep 11434 uit op de server en curl -m 5 http://YOUR_SERVER_IP:11434/api/version vanaf een andere machine. ss dat 127.0.0.1:11434 toont en een remote curl die een time-out geeft, is de combinatie van antwoorden die u wilt zien. ss dat 0.0.0.0:11434 of *:11434 toont terwijl de remote curl JSON teruggeeft, betekent dat de volledige API bereikbaar is. Test nooit met curl op de server zelf, omdat loopback antwoord geeft ongeacht wat het bind-adres is.

Kan ik de poort gewoon verplaatsen van 11434 naar een willekeurig nummer?

Nee, en de reden daarvoor is belangrijk. Een andere poort vertraagt niets, behalve een scan van één enkele poort. Scanners doorlopen het hele bereik en één verzoek aan /api/tags identificeert de service, ongeacht op welke poort het aankwam. Het verplaatsen van de poort doorbreekt bovendien alle standaardinstellingen van clients en maakt uw eigen configuratie later lastiger te doorgronden. Bind in plaats daarvan aan loopback; dit verwijdert de listener in plaats van deze te verplaatsen.

Iemand heeft mijn openstaande Ollama bereikt. Wat moet ik controleren?

Bind de service eerst aan 127.0.0.1 en herstart deze, zodat de blootstelling stopt voordat u begint met onderzoeken. Voer daarna journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 uit om te zien welke externe adressen welke endpoints hebben aangeroepen en wanneer. Vergelijk ollama list met de modellen die u bedoeld had te hebben, aangezien /api/pull niet geauthenticeerd is en een model dat u niet zelf heeft gepulled zowel schijfgebruik als bewijslast is. Controleer de vrije ruimte met df -h. Ollama registreert geen prompttekst op het standaard logniveau; u heeft dus een overzicht van wie er vroeg en voor welk model, maar niet van wat er werd gegenereerd.