Ollama API No Get Password: How You Go Secure Am
Ollama API no require password. Anybody wey reach port 11434 fit run models, download new ones, or delete dem. 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 dey 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 plenty people dey make, wey be to open the listener so another machine fit use the model, na the change wey remove every protection at once.
Wetin open port 11434 dey expose
Every endpoint. No read-only mode dey, and no separate admin port dey. These na the real requests, directed to 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. If your plan get fair-use CPU allowance, sustained load mean say stranger dey spend your allowance, and keeping AI workload costs under control on a VPS go harder once you no be the only caller.
/api/pulldey write to your disk. Each model fit take from two to forty gigabytes. Pull loop fit fill the volume, and full disk fit break every other service for the machine, no be only Ollama.- Requests dey enter your process and get logged. For the default log level, Ollama dey record metadata only, so you get the endpoint, status, latency, and client address, but no prompt text. Even so, na record of who use your machine and wetin dem use am do, dey sit inside your journal, and you no choose to collect am.
/api/deletedey remove models. To get dem back, 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. If 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 named 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.pubThat key dey point outward. ollama signin dey register the public half with your ollama.com account. Na this one 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. That 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 dey call the hosted API for https://ollama.com/api. Na credential for their service, and na you as the client dey use am. 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 dey show the loopback address:
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))The exposed result dey show 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 machine, 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 anybody wey ask fit reach the whole API. If you test with curl for the server itself, e no prove anything, because loopback always dey answer.
Exposure normally happen for one of two ways. The first one na deliberate edit, because person need another machine to reach the model:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"That one line na the complete exposure. The second way na Docker, and e no require you to edit anything at all. That one get its own section below.
Defence 1: keep am for localhost and tunnel enter
Start with this one. E no need new software, and e no create any credential wey fit leak. The port no dey 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"That 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 with its 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 open port 11434 for your laptop and send 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 a tunnel wey connect successfully mean say SSH dey work, but Ollama no dey listen for the server side. So check ss there before you change 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 for that network instead of 0.0.0.0:
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"The port go then dey only for an interface wey you need key to join. This one still work even if firewall mistake happen, because rule wey accidentally allow everybody still no fit expose listener wey public interface no get.
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 go terminate TLS (transport layer security) and reject request wey no get 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 36nginx 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 prevent one failure wey you for otherwise encounter.
if inside location block normally no be good idea for nginx, but body wey exactly be return na one of the two forms wey dey behave predictably, so this use safe.
location = /api/pull na exact match, and nginx rank exact matches above location / prefix, so e go reject those three endpoints before e even consider token. Valid token then give inference access, not 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 fit make debugging confusing. OLLAMA_ORIGINS na the other lever, for browser client wey need specific origin allowed.
proxy_buffering off; matter because Ollama dey stream response token by token. When buffering dey on, nginx hold the stream and deliver everything as one piece 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, map block dey wrong scope. E suppose dey for http level, so put am inside file under /etc/nginx/conf.d/ or above server block, never inside server.
Caddy fit 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 bcrypt hash wey e expect. One naming trap: directive name na basicauth before Caddy v2.8, and 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 be this. Every client wey hold am get the same access, and to revoke am mean say you must edit 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, shared token go soon finish. You no fit know which client cause the load, and you no fit block one without blocking everybody. Gateway dey sit where proxy dey sit, e dey speak the same OpenAI-compatible API, e dey issue separate key for each client, and e dey record wetin each key use. Self-hosted LiteLLM gateway na the usual answer, and e dey add per-key budgets plus request logs on top of access control.
The rule from defence 1 no change. Ollama dey bind to 127.0.0.1, na gateway be the only process wey dey talk to am, and na gateway be the only service wey get public listener. Gateway for server wey port 11434 still open to everybody na just decoration, because callers fit simply bypass am.
Firewall trap: container port publish korile UFW skip kori jai
Na e karone exposed instances dey 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 address 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. Before routing decision happen, destination don already change to container address. So packet dey forward instead of local delivery, and e dey 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 to add one address for 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 short form of -p 0.0.0.0:11434:11434. When you name 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 this case because the models dey inside named ollama volume, not 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 a new VPS need cover the base wey this one dey use. For Rocky or AlmaLinux, UFW no dey available to configure, so the same base policy wey firewalld use na where you suppose start instead.
Process dey run as who
Linux install script dey create one dedicated account and run the service under that account:
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaThe unit for /etc/systemd/system/ollama.service dey set User=ollama and Group=ollama. Make you leave am like that. A quick ollama serve wey you start by hand for terminal dey run as the user wey you log in with. If na root, unauthenticated API dey write files as root. Check which one e be:
ps -o user= -C ollamaThe answer suppose be ollama. Anything else mean say process wey person start by hand dey run alongside the unit, or e dey run instead of the unit. This same approach apply to every daemon wey you add later, and running services as least-privilege users 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 get refused. If you build proxy, the same two paths for the proxy hostname suppose return 401 without credentials, and real JSON when credentials dey present.
Then read the access log one time, because e go show whether anybody discover the port while 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 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 one time after Ollama bind to loopback, because na only that address connection fit come from. If public address dey that column, na request from outside, and timestamp go show when e happen. If that command produce no output at all, na the result you want. If this model side still new to you, how to run Ollama for a VPS cover the installation, model sizing, and memory limits wey decide wetin go actually load.
FAQ
Ollama get API key or password?
No. The server wey you run no get any authentication, and the official documentation talk say you no need authentication to reach the API. The two things wey people dey call "Ollama API key" actually serve different purpose. 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 sends to the hosted API for https://ollama.com/api. Your own ollama serve no read either of dem, so network or proxy wey dey in front must handle access control.
OLLAMA_HOST=0.0.0.0 safe if I get firewall?
Only if nothing else dey write firewall rules for that box. 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. That trust go break immediately Docker publish a port, because the DNAT rule wey Docker add to nat table dey evaluate before packet reach INPUT chain where UFW dey, so packet go forward and UFW no go see am. Binding to 127.0.0.1 or private tunnel address remove 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 run curl -m 5 http://YOUR_SERVER_IP:11434/api/version from another machine. If ss show 127.0.0.1:11434 while remote curl dey timeout, 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. No ever 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 arrive on. Moving the port still break every client default and make your own setup harder to understand later. Bind to loopback instead. This one remove the listener instead of relocating 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 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 model wey you no pull mean 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.