Note
Local Is Not Private: Who Else Can Reach Your Model Server
Local AI runners listen on ports. Bind, auth, CORS and Docker publishing decide who can reach Ollama, llama.cpp and LM Studio — literacy, not fear.
2026-11-09 · local-ai, ollama, llama-cpp, lm-studio, inference-server, bind-address, authentication, cors, docker, firewall, wireguard, tailscale, ssh-tunnel, threat-model, civil-liberties
Listen · 11 min · AI voice

Running a model on your own hardware solves a real problem. Your prompts don't travel to a vendor and nobody bills you per token. But every popular local runner ships as a server: an HTTP API on a port, waiting for requests. "Where does the model run?" and "who can talk to it?" are separate questions, and answering the first one doesn't answer the second.
Thesis: local describes where inference happens. Private describes who can reach it. You get the second from four settings working together: the bind address, authentication, which browser origins are allowed, and how ports get published. Each runner ships a sensible default. Each also has a one-line change, often copied from a setup guide, that quietly opens the door.
Mechanism one: the bind address decides who can knock
A server socket listens on an address. 127.0.0.1 (loopback) means "only this machine." 0.0.0.0 means "every network interface I have": home Wi-Fi, office LAN, VPN, hotel network.
- Ollama: the FAQ says it "binds 127.0.0.1 port 11434 by default," and you change that with
OLLAMA_HOST. The config source describes the variable as "IP Address for the ollama server (default 127.0.0.1:11434)." - llama.cpp: the server README documents
--hostwith "default: 127.0.0.1". The current README lists port 9931 as the default, though its Docker examples use--host 0.0.0.0 --port 8080. - LM Studio:
lms server start --binddefaults to127.0.0.1. The CLI docs and the Serve on Local Network page give the same warning: "Any bind other than 127.0.0.1 exposes the server beyond localhost; we recommend enabling authentication."
All three defaults are safe. The risk arrives when you want a second device to use the model and the quickest fix is the wildcard bind.
Mechanism two: nobody asks who you are
Once a request reaches the port, the next question is whether the server checks credentials. By default, none of these three do.
- Ollama's authentication page says it plainly: "The local API at
http://localhost:11434does not require authentication." API keys exist only for ollama.com's cloud. - llama.cpp offers
--api-keyand--api-key-file, but the default is "none." Even with a key set, the health endpoint "is public (no API key check)." - LM Studio's authentication docs say: "By default, LM Studio does not require authentication for API requests." You can turn on a "Require Authentication" switch and issue named tokens with set permissions.
Wildcard bind plus no auth means anyone who can route a packet to you has your API access.
What an open endpoint actually allows
The worst case here isn't the one people usually imagine. Ollama's chat API is stateless. Per the API reference, the client sends messages, which "can be used to keep a chat memory." The server doesn't keep a browsable transcript of your past conversations. A stranger on the port isn't reading your old chats through this API.
What they can do is drive the server as if they were you. The Ollama API documents:
POST /api/generateand/api/chat: your GPU, your electricity, your model, running their workload.GET /api/tagsand/api/ps: a list of what you have installed and what's loaded right now.POST /api/pull: download models from the library onto your disk.DELETE /api/delete: "Delete a model and its data."POST /api/createand/api/copy: build new models from existing ones, with their ownsystemprompt andtemplate.
That last pair moves the risk from abuse to integrity. A model you didn't configure, with instructions you didn't write, can sit next to the ones your scripts call.
llama.cpp has a different surface. /slots is "enabled by default" and reports per-slot state and sampling parameters. Slot save and restore write prompt caches into a directory you set with --slot-save-path (disabled by default); erase clears a slot's cache. /props is read-only unless you start the server with --props. More importantly, --tools can expose built-in agent tools including read_file, write_file and exec_shell_command, and the flag carries the warning "do not enable in untrusted environments." On an open bind with those tools enabled, callers can read, write and run shell commands on the host — not just sample tokens.
Mechanism three: your browser is a client too
Every web page you open runs code that can try to send requests to localhost. Two controls decide what happens next.
CORS decides which web origins your browser will let read the server's responses. Ollama's source allows localhost, 127.0.0.1 and 0.0.0.0 on any port, plus app://, file://, tauri://, vscode-webview:// and vscode-file:// schemes. OLLAMA_ORIGINS adds to that list. The FAQ shows how to allow every Chrome, Firefox and Safari extension at once, which means trusting every extension you'll ever install. The llama.cpp README is candid that by default its server "reflects any Origin header back with credentials allowed," and calls that fine only "as long as the server only exposes stateless, read-only endpoints." Enabling --tools or --agent switches the default to localhost-only. LM Studio ships with CORS off and labels --cors a "Security Risk."
The Host-header check defends against DNS rebinding, where a hostile domain re-points itself at 127.0.0.1. Ollama's routes.go runs a middleware that rejects requests whose Host isn't localhost, a private or local IP, the machine's own hostname, or a .localhost/.local/.internal name. But the middleware skips that check entirely when the server isn't bound to loopback. Widening OLLAMA_HOST doesn't just add listeners. It also turns off this guard.
Mechanism four: Docker can publish past your firewall
Containers add a trap for careful people. Ollama's Dockerfile sets OLLAMA_HOST=0.0.0.0:11434. That's correct inside a container, because otherwise nothing could reach it. The Docker guide then runs it with -p 11434:11434.
Docker's port-publishing docs are blunt: "Publishing container ports is insecure by default." A published port is available "not only to the Docker host, but to the outside world as well," unless you put 127.0.0.1 in the flag. And on Ubuntu's ufw, Docker's firewall docs explain that published container traffic "gets diverted before it goes through the ufw firewall settings," because Docker routes it in the NAT table. Your ufw deny 11434 can be on screen and still do nothing. The fixes Docker documents are -p 127.0.0.1:11434:11434, or filtering rules in the DOCKER-USER chain (iptables docs).
Safer patterns, from smallest to largest
- Stay on loopback. If only this machine needs the model, change nothing, and bind container ports to
127.0.0.1. - SSH tunnel for one other device.
ssh -Lforwards a local port over an authenticated, encrypted session. The ssh manual notes that abind_addressof "localhost" keeps the forwarded port "for local use only." The server never leaves loopback. - Private overlay for several devices. WireGuard's cryptokey routing ties each peer's public key to its allowed tunnel IPs. On receive, that list "behaves as a sort of access control list." Tailscale Serve proxies a local service to your tailnet only, and access rules apply to it. Two caveats from the docs: ACLs are deny-by-default, but a policy file with no
aclssection allows everything, and Funnel, the public variant, is a separate feature you may want to switch off. - Reverse proxy with real auth. Ollama's FAQ shows an Nginx
proxy_passexample and ngrok or Cloudflare Tunnel one-liners. Note what they leave out: none of those examples adds authentication. A tunnel without an auth layer puts an unauthenticated API on the public internet.
This matters most for people on networks they don't control (shared offices, conference Wi-Fi, an NGO's borrowed LAN), who often chose local AI for privacy in the first place.
What this is not
This note doesn't rank runners or say one is "secure" and another isn't. All three ship loopback defaults, and their docs warn honestly about changing them. It isn't a production hardening checklist; TLS, rate limiting and multi-tenant isolation are out of scope. It doesn't claim exposed servers leak your past chats through the documented API. The fetched Ollama docs describe a stateless chat endpoint; logging by proxies or front-ends is a separate question. It doesn't quote counts of exposed servers on the internet, because we haven't checked a primary source for them. And it doesn't revisit what local tools do once a request is legitimately inside, which earlier notes cover.
Related on CryptoDecentral
- Decentralised / local AI pillar
- Local and Peer-to-Peer AI: Data Residency Without the Hype
- Inference Residency Is Not Action Residency: Tools, MCP, and Local LLMs
- Docker Is the Starting Line, Not the Boundary
- Local Chat Is Not Data Residency: RAG, Embeddings, and Vector Stores
- A Threat Model Is Not a Fear List
Further reading (primary)
- Ollama FAQ: exposing on a network, proxies, origins
- Ollama API reference and authentication
- Ollama envconfig (OLLAMA_HOST, OLLAMA_ORIGINS) and routes.go Host-header middleware
- llama.cpp server README: --host, --api-key, CORS, --tools
- LM Studio: Serve on Local Network, Server Settings, Authentication
- Docker: port publishing, packet filtering and firewalls
- WireGuard: cryptokey routing · Tailscale Serve · Tailscale ACLs
- OpenBSD ssh(1) manual: -L forwarding
Sources
- https://github.com/ollama/ollama/blob/main/docs/faq.mdx
- https://github.com/ollama/ollama/blob/main/docs/api.md
- https://github.com/ollama/ollama/blob/main/docs/api/authentication.mdx
- https://github.com/ollama/ollama/blob/main/envconfig/config.go
- https://github.com/ollama/ollama/blob/main/server/routes.go
- https://github.com/ollama/ollama/blob/main/docs/docker.mdx
- https://github.com/ollama/ollama/blob/main/Dockerfile
- https://github.com/ggml-org/llama.cpp/blob/master/tools/server/README.md
- https://lmstudio.ai/docs/developer/core/server/serve-on-network
- https://lmstudio.ai/docs/developer/core/server/settings
- https://lmstudio.ai/docs/developer/core/authentication
- https://lmstudio.ai/docs/cli/serve/server-start
- https://docs.docker.com/engine/network/port-publishing/
- https://docs.docker.com/engine/network/packet-filtering-firewalls/
- https://docs.docker.com/engine/network/firewall-iptables/
- https://man.openbsd.org/ssh
- https://www.wireguard.com/
- https://tailscale.com/kb/1312/serve
- https://tailscale.com/kb/1018/acls