How to Secure Ollama API Wey No Get Password
Ollama API get no authentication by default: anybody wey reach port 11434 fit run, download, or delete models. See the three fixes, in order.
Ollama API no get password
Ollama API no get authentication. No user dey, no password dey, no key check dey, and no allowlist dey anywhere for the server wey you run. Anything wey fit open TCP connection to port 11434 fit list your models, run dem, download new ones, and delete the ones wey you get.
The official documentation talk am clearly: "No authentication is required when accessing Ollama's API locally via http://localhost:11434." The word locally carry the whole security model. Ollama bind to 127.0.0.1 by default, so for laptop, loopback interface na the access control. If you move that listener go public address, the access control don disappear, because nothing replace am.
Na why this matter for VPS (virtual private server). The default setting safe. The first change wey most people make, na to open the listener so another machine fit use the model. Na that change dey remove every protection at once.
Wetin open port 11434 dey expose
Every endpoint. No read-only mode dey, and no separate admin port dey. Na these real requests be this, but dem target server address instead of 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"}'For operator side, four things dey go wrong:
- Your CPU or GPU dey run inference for another person. For plan wey get fair-use CPU allowance, sustained load mean say stranger dey spend your allowance, and keeping AI workload costs under control on a VPS dey become much harder once you no be the only caller.
/api/pulldey write to your disk. Each model dey use from two to forty gigabytes. Pull loop fit fill the volume, and full disk fit break every other service for the box, no be only Ollama.- Requests dey enter your process and get logged. For default log level, Ollama dey record metadata only, so you go get the endpoint, status, latency, and client address, but no prompt text. That one still be record of who use your box and wetin dem use am do, sitting inside your journal, and na you no choose to collect am.
/api/deletedey remove models. To get dem back mean say you go download dem again with your own bandwidth.
None of this need exploit. Na the documented API dey behave exactly as dem design am.
Ed25519 key no be access control
If you search for "Ollama API key", you go see two different things. None of dem be password for your server. Once you separate dem, most of the confusion go clear.
The first one na the identity key pair. Ollama dey generate Ed25519 key pair the first time e run. For Linux, the install script dey create system user wey dem name ollama, with home directory for /usr/share/ollama. So the key pair dey here:
/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pubThis key dey prove identity to outside service. ollama signin dey register the public half with your ollama.com account. Na this key dey authorise you to push model to the registry or pull private one. E proves your machine to ollama.com. E no ask anything from clients wey dey connect to your machine. If you delete am, rotate am, or never create am, e no change who fit call your API.
The second one na OLLAMA_API_KEY. This variable dey hold key wey you create for https://ollama.com/settings/keys. Your client dey send am as Authorization: Bearer $OLLAMA_API_KEY when e call the hosted API for https://ollama.com/api. Na credential for their service, and na you dey use am as the client. Your own ollama serve no dey read am. If you set OLLAMA_API_KEY for your VPS, e no put password for your VPS.
So, no setting dey wey you need switch on. The three defences below dey work the same way: make the port no reachable, then put something for front wey go check access.
Check wetin your server dey listen to now
sudo ss -tlnp | grep 11434The safe result name the loopback address:
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))The exposed result name every interface:
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))0.0.0.0 mean all IPv4 addresses for the box, including the public one. *:11434 and [::]:11434 mean the same thing with IPv6 included.
Now confirm am from outside. Run this for your laptop, no be for the server:
curl -m 5 http://YOUR_SERVER_IP:11434/api/versioncurl: (28) Connection timed out after 5001 milliseconds na the answer wey you want, and curl: (7) Failed to connect ... Connection refused too. JSON object wey get version field mean say anybody wey ask fit reach the whole API. Testing with curl for the server itself no prove anything, because loopback go always answer.
Exposure usually happen for one of two ways. The first one na deliberate edit, because person need make second machine reach the model:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"That one line na the whole exposure. The second way na Docker, and e no ask you edit anything at all. That one get im own section below.
Defence 1: keep am for localhost and tunnel enter
Try this one first. E no need new software, and e no create any credential wey fit leak. The port no dey exist for any public interface, so scanning no fit find am.
Set the bind address directly instead of depending on the default:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"This one write /etc/systemd/system/ollama.service.d/override.conf. Apply am and check:
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434ss suppose now show 127.0.0.1:11434. If e still show 0.0.0.0, another drop-in file dey take priority. Run systemctl cat ollama.service to list the unit and every drop-in together with the path, then delete the old one.
To use the model from your laptop, forward the port through SSH:
ssh -N -L 11434:127.0.0.1:11434 you@your-server-L 11434:127.0.0.1:11434 opens port 11434 for your laptop and sends anything wey enter there go 127.0.0.1:11434 as the server see am. -N tell SSH make e no run remote command, so the process just keep the tunnel open. While e dey run, this one go work for your laptop:
curl -s http://localhost:11434/api/tagsYou go meet two failures. bind [127.0.0.1]:11434: Address already in use mean say your laptop dey run its own Ollama for that port, so choose another local port with -L 11500:127.0.0.1:11434 and point your client to 11500. Empty reply through tunnel wey connect well mean say SSH dey work, but Ollama no dey listen for the server side. Check ss for there before you touch the SSH command.
For several client machines, private network better pass one tunnel for each person. Put the machines on WireGuard or Tailscale, then bind Ollama to the address wey e get for that network instead of 0.0.0.0:
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"The port go then exist only on interface wey you need key to join. This one still work even if firewall rule get mistake, because rule wey accidentally allow everybody still no fit expose listener wey public interface no hold.
Defence 2: reverse proxy wey dey check bearer token
When something for public internet must call the model, keep Ollama for loopback and put proxy for front of am. The proxy dey terminate TLS (transport layer security) and reject request wey no get the correct header. Ollama still dey accept connection only from 127.0.0.1, so na proxy be the only way enter.
Generate real token first. No invent one by hand:
openssl rand -base64 36Na nginx site wey dey check am:
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;
}
}Five lines dey do real work there, and each one dey stop one failure wey you for otherwise encounter.
if inside location block normally no good idea for nginx, but body wey exactly be return na one of the two forms wey behave predictably, so this usage safe.
location = /api/pull na exact match, and nginx dey rank exact matches above the location / prefix. So e go refuse those three endpoints before e even check the token. Valid token then give inference access, but e no give permission to fill your disk.
proxy_set_header Host 127.0.0.1:11434; matter because Ollama dey inspect incoming Host and Origin headers. If you pass proxy public hostname straight through, e fit produce 403 Forbidden wey come from Ollama instead of nginx, and this one hard to debug. OLLAMA_ORIGINS na the other control, for browser client wey need make you allow specific origin.
proxy_buffering off; matter because Ollama dey stream response token by token. When buffering dey on, nginx hold the stream and deliver everything at the end, so your client go look frozen throughout the generation.
proxy_read_timeout 600s; matter because nginx default na 60 seconds. Long generation for CPU fit pass that time easily; client go receive 504 Gateway Time-out, and /var/log/nginx/error.log go record upstream timed out (110: Connection timed out) while reading response header from upstream. The request still dey work. nginx just give up on am.
Reload and test both paths:
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/tagsThe first one suppose print 401. The second one suppose print your model list. If the first one also return the model list, the map block dey the wrong scope. E belong for http level, so put am inside file under /etc/nginx/conf.d/ or above the server block. Never put am inside server.
Caddy dey do the same work with basic authentication for four lines, and this one fit browser client better than bearer token:
llm.example.com {
basic_auth {
apiuser PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:11434
}Run caddy hash-password to produce the bcrypt hash wey e expect. One naming trap: the directive name na basicauth before Caddy v2.8, but now na basic_auth. So config wey you copy from older guide go refuse to load, and Caddy go name the directive wey e no recognise.
Any proxy wey you choose, na one shared secret for everybody. Every client wey hold am get identical access. To revoke am, you must edit the config and update every caller at the same time.
Defence 3: gateway wey dey issue key for each client
Once more than one person or application dey call the model, one shared token no dey enough again. You no fit know which client cause the load, and you no fit block one without blocking everybody. Gateway go sit where proxy dey sit, e go speak the same OpenAI-compatible API, issue separate key for each client, and record wetin each key use. Self-hosted LiteLLM gateway na the usual answer, and e add budget for each key plus request logs on top of access control.
The rule from defence 1 no change. Ollama dey bind to 127.0.0.1, na only gateway dey talk to am, and na only gateway service get public listener. Gateway for machine wey port 11434 still open to everybody na just decoration, because callers fit simply bypass am.
Firewall trap: published container port dey bypass UFW
Na why exposed instances dey show for servers wey owners configure firewall correctly.
UFW (uncomplicated firewall) dey write im rules inside INPUT chain for kernel filter table, and INPUT dey handle packets wey target the host itself. Docker -p flag dey write destination NAT (network address translation) rule inside PREROUTING chain for nat table, wey kernel dey evaluate before e decide where packet dey go. By the time routing decision happen, destination don already change to container address. So packet dey forward instead of dem delivering am locally, and e pass through FORWARD instead of INPUT. UFW INPUT rules no dey checked at all, so packet dey go around firewall instead of passing through am.
Na why this sequence leave port 11434 open to internet:
sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollamaand sudo ufw status still report say firewall dey active with default deny. Both readings fit correct at the same time. Na exactly why people dey trust the wrong one. You fit see the rule wey cause am:
sudo iptables -t nat -L DOCKER -nThe fix na one address for the publish flag:
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 na shorthand for -p 0.0.0.0:11434:11434. When you specify 127.0.0.1, e bind the host side of the mapping to loopback. So your SSH tunnel and reverse proxy still fit reach am, but internet no fit. Recreating the container safe for here because the models dey inside named ollama volume, no be inside the container.
Confirm say both views agree:
docker port ollama
sudo ss -tlnp | grep 11434docker port ollama suppose print 11434/tcp -> 127.0.0.1:11434. If e print 0.0.0.0:11434, you still dey exposed. Once you understand this mechanism, e apply to every container wey you ever publish: why Docker published ports dey bypass UFW explain DOCKER-USER chain and the rules wey survive Docker restart. If you still dey build the host policy itself, the UFW rules wey new VPS need explain the base wey this setup dey use.
Who process dey run as
The Linux install script dey create dedicated account and run the service under am:
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaThe unit for /etc/systemd/system/ollama.service then set User=ollama and Group=ollama. Leave am like that. Quick ollama serve wey you start by hand for terminal dey run as whoever you use log in as. If na root, unauthenticated API dey write files as root. Check which one e be:
ps -o user= -C ollamaThe answer suppose be ollama. Any other answer mean say process wey person start by hand dey run alongside the unit or instead of am. This same approach apply to every daemon wey you add later, and running services as users with least privilege handle am properly.
How to check whether Ollama API endpoint secure
Any option wey you choose, one test go settle am, and you must run am from another machine:
curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tagsBoth suppose time out or dem suppose get refused. If you build proxy, the same two paths for the proxy hostname suppose return 401 without credentials, and real JSON with credentials.
Then read the access log once, because e go show whether anybody discover the port when e dey open:
journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1Ollama dey write one line for every request and e dey include the client address:
[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"Every line suppose show 127.0.0.1 once Ollama bind to loopback, because na only that address connection fit come from. Public address for that column mean say request come from outside, and timestamp go show when e happen. If that command no produce any output, na the result wey you want. If this model side still new to you, how to run Ollama for VPS explain the installation, model sizing, and memory limits wey decide wetin go load.
FAQ
Ollama get API key or password?
No. The server wey you run no get any authentication at all, and the official documentation talk say you no need authentication to reach the API. The two things wey people dey call “Ollama API key” point to different places. The Ed25519 pair for /usr/share/ollama/.ollama/ proves your machine to ollama.com, so you fit push models and pull private ones. OLLAMA_API_KEY na credential wey your client dey send to the hosted API for https://ollama.com/api. Your own ollama serve no dey read either of dem, so network or proxy for front of am must provide access control.
OLLAMA_HOST=0.0.0.0 safe if I get firewall?
Only while nothing else dey write firewall rules for that machine. 0.0.0.0 mean say the listener really dey exist for the public interface, and you dey trust firewall alone to keep am unreachable. This trust go break once Docker publish a port, because the DNAT rule wey Docker add to the nat table dey evaluate before packet reach the INPUT chain where UFW dey, so the packet go forward and UFW no go see am. Binding to 127.0.0.1 or private tunnel address removes the listener from public interface, so firewall mistake no get anything left to expose.
How I fit check whether my Ollama port open to internet?
Run sudo ss -tlnp | grep 11434 for the server, and curl -m 5 http://YOUR_SERVER_IP:11434/api/version from another machine. If ss show 127.0.0.1:11434 and remote curl time out, na the two answers wey you want. If ss show 0.0.0.0:11434 or *:11434 while remote curl return JSON, the full API dey reachable. Never test with curl for the server itself, because loopback go answer based on whichever bind address dey configured.
I fit just move the port from 11434 to something random?
No, and e good make we explain why. Different port no go slow anything except scan of one single port. Scanners dey check the whole range, and one request to /api/tags go identify the service, no matter which port e use arrive. Moving the port also go break every client default and make your own setup harder to understand later. Bind to loopback instead. That one removes the listener instead of moving am.
Person reach my open Ollama. Wetin I suppose check?
Bind am to 127.0.0.1 and restart the service first, so the exposure go stop before you start the investigation. Then run journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 to see which outside addresses call which endpoints and when. Compare ollama list with the models wey you intend to get, because /api/pull no get authentication and any model wey you no pull na both disk usage and evidence. Check free space with df -h. Ollama no dey record prompt text for the default log level, so you get record of who ask and which model dem ask for, but not wetin the system generate.