DEV Community

John
John

Posted on

Ollama Has No API Authentication: How To Properly Gate Port 11434

Ollama has no username, no password and no API key. Anything that can reach TCP port 11434 can list your models, run inference on your GPU, pull a 40 GB model onto your disk and delete every model you have, with a single curl command and no credential. The fix is not a setting inside Ollama, because there is not one: you keep the default bind on 127.0.0.1, and if you genuinely need remote access you put a bearer token proxy, an SSH tunnel or a mesh VPN in front of it. Everything else in this article is choosing which of those three, and proving you got it right.

TL;DR by reader profile:

  • The single laptop user (Maya, a writer running llama3.2 on a MacBook for offline drafting): keep the default 127.0.0.1 bind and never touch OLLAMA_HOST, because nothing outside the laptop has any reason to reach the API.
  • The home GPU box owner (Tom, one desktop with an RTX card in the study, phone and tablet elsewhere in the house): use a mesh VPN such as Tailscale or WireGuard, because it gives you remote access without any port ever being open to the network or the router.
  • The laptop plus workstation developer (Priya, coding on a thin laptop against a beefy machine two rooms away): use an SSH tunnel, because it needs zero changes on the Ollama side and dies the moment you close the terminal.
  • The self-hoster running a web chat UI (Ben, Open WebUI on a rented VPS with a public domain): terminate HTTPS at a reverse proxy that checks a bearer token, and never publish 11434 itself, because the browser tier and the model tier need separate trust.
  • The small team sharing one model server (a six person studio pooling one GPU): use a proxy with per user tokens and rate limits, or mutual TLS, because you need to revoke one person without rotating everyone.
  • Anyone who already set OLLAMA_HOST=0.0.0.0 (following a blog post to make a client work): stop and audit before hardening, because an exposed endpoint may already have models on disk that you did not pull.

The real tradeoff is this: every method that makes a new client easy to connect also widens the set of machines that can reach your models, and you cannot optimise both at once.


Table of contents


What is actually listening on port 11434 when Ollama runs?

When you install Ollama, you get two things: a command line client and a long running HTTP server. The server is the part that matters here. It listens on TCP port 11434 and speaks plain HTTP, not HTTPS, and it answers every request it receives without asking who sent it.

  • A REST API, not a chat program: the ollama run llama3.2 command you type is a thin client that talks to the same HTTP endpoint any other machine would use, so anything curl can send, a stranger can send.
  • A background service that starts itself: on Linux it is usually a systemd unit called ollama.service, on macOS it is the menu bar app, and on Windows it is a tray process, which means the port is open long after you close the terminal you launched it from.
  • Full lifecycle control, not just inference: the API covers listing, pulling, creating, copying and deleting models, so it is an administrative interface as much as a generation interface.
  • Direct access to your model directory: models live under ~/.ollama/models on Linux and macOS, and the API can add to or remove from that directory without touching the shell.
  • No credential layer of any kind: there is no login, no API key setting and no per client identity, so the only access control you have is which network interfaces the port is bound to.

Where that port lives depends on where you run Ollama: a laptop, a home server, a NAS, a rented VPS, or a managed Personal Cloud Server. Yundera is a managed Personal Cloud Server, built on CasaOS, that runs self-hosted apps as Docker containers on a server dedicated to the user.


What can somebody do with an unauthenticated Ollama API?

Treat reachability as full control. There is no read only mode, so anyone who can open a socket to the port holds the same powers you do from your own shell.

  • Inventory your models: a single curl http://your-host:11434/api/tags returns every model name, size and digest on the machine, which tells an intruder both what you use the box for and how much disk you have committed.
  • Run inference on your hardware for free: POST /api/generate and POST /api/chat execute on your GPU and your electricity bill, with no rate limit and no quota, and an abuser can point a public chat frontend at your endpoint and let strangers use it.
  • Fill your disk on demand: POST /api/pull downloads any model from the registry, and large models run to tens of gigabytes each, so a loop of pull requests is a straightforward way to exhaust storage until the host stops working.
  • Delete everything you have downloaded: DELETE /api/delete removes a model with one request and no confirmation prompt, so hours of downloads disappear without a trace in any interface you normally watch.
  • Read what you are currently doing: GET /api/ps lists models loaded in memory right now, which leaks activity patterns even when no prompt content is exposed.
  • Create custom models on your machine: POST /api/create builds a new model entry from a Modelfile, including a system prompt of the attacker's choosing, so a client of yours can be silently served a modified model under a familiar name.

None of this requires an exploit or an unpatched version. It is the documented API behaving exactly as designed.


Is binding to 127.0.0.1 enough, and when does that guarantee break?

Yes, with one condition: a process bound to 127.0.0.1 is unreachable from any other machine, because the kernel refuses packets arriving on a physical interface for a loopback address. No firewall rule is doing that work. The bind itself is the boundary. The problem is how many ordinary situations quietly move the bind somewhere else.

  • You set OLLAMA_HOST to make one client work: the usual advice for connecting a phone, a second laptop or a container is OLLAMA_HOST=0.0.0.0:11434, which replaces a boundary that was airtight with none at all, on every interface at once including any public one.
  • Loopback is per machine, not per user: every local account, every browser extension and every background process on that host can reach 127.0.0.1:11434, so on a shared workstation the bind protects you from the network and from nobody else.
  • Windows Subsystem for Linux crosses the line: WSL2 runs on its own virtual network, so a server bound to loopback inside WSL and a server bound to loopback on Windows are two different boundaries, and people usually resolve the confusion by binding wide.
  • A VPN or mesh interface is still an interface: if you bind to 0.0.0.0 expecting only your VPN subnet to reach it, the same socket answers on Ethernet and Wi Fi too.
  • IPv6 has its own loopback: binding to 127.0.0.1 does not cover ::1, and binding to [::] covers far more than you probably intend.

The rule to keep: change the bind only when you have already decided what will check the requests.


How do I check right now whether my Ollama is reachable from outside?

Do this before you change anything. Five minutes of checking tells you whether you are hardening a closed door or cleaning up after an open one.

  • Look at the bind address on the host itself: run ss -tlnp | grep 11434 on Linux, lsof -nP -i :11434 on macOS, or netstat -ano | findstr 11434 on Windows, and read the left hand address, because 127.0.0.1:11434 is safe and 0.0.0.0:11434 or [::]:11434 means every interface is answering.
  • Test from a second device on the same network: from a phone or another laptop, run curl -m 5 http://192.168.1.50:11434/api/tags with your machine's local IP, and a JSON model list coming back proves the LAN can reach it.
  • Test from outside your network: turn Wi Fi off on your phone so it uses mobile data, then try the same request against your public IP, because that is the only test that distinguishes a LAN exposure from an internet exposure.
  • Check the router for a forwarding rule: open the port forwarding or virtual server page and look for anything sending 11434 inward, since a rule added months ago for a different purpose can survive a router reboot and a firmware update.
  • Audit the model directory for things you did not pull: list ~/.ollama/models and compare it against what you remember downloading, because an unfamiliar model or a sudden jump in disk usage is the clearest sign someone else has been using the API.

Write the results down. You will repeat this test after every change in the sections that follow.


How do I set OLLAMA_HOST correctly on Linux, macOS and Windows?

The variable has to reach the background service, not your terminal. Exporting OLLAMA_HOST in a shell changes where the client looks, not where the server listens, which is the single most common reason a change appears to do nothing.

Platform Where the value belongs What makes it take effect
Linux with systemd sudo systemctl edit ollama.service, then add Environment="OLLAMA_HOST=127.0.0.1:11434" under a [Service] block sudo systemctl daemon-reload followed by sudo systemctl restart ollama
macOS desktop app launchctl setenv OLLAMA_HOST "127.0.0.1:11434" so the value is visible to apps launched by the session Quit Ollama from the menu bar and start it again, since the running process keeps its old environment
Windows User environment variables in the System Properties dialog, one entry named OLLAMA_HOST Exit the tray icon completely, then relaunch, because a minimised process is still the old one
Docker container An -e OLLAMA_HOST=... flag or an environment: entry, paired with a publish flag such as -p 127.0.0.1:11434:11434 Recreate the container, since environment variables are fixed at creation

After any of these, repeat the two device test from the previous section rather than trusting the config file. The value is only correct if a request from another machine now fails.

Where you run the server decides which row applies to you: a laptop and a self managed VPS use the platform rows, while a NAS, a home server running containers and a managed Personal Cloud Server such as Yundera all use the Docker row, because apps there run as Docker containers on a server dedicated to the user.


Does OLLAMA_ORIGINS stop a website from talking to my local models?

Partly, and only inside a browser. OLLAMA_ORIGINS sets which web origins get an Access-Control-Allow-Origin header back. That is a rule the browser enforces on the page, not a rule the server enforces on the request. Understand the difference before you rely on it.

  • It does block the ordinary case: a random site you visit cannot script a POST to http://localhost:11434/api/chat, because a JSON content type triggers a preflight OPTIONS request first, and a rejected preflight means the real request is never sent.
  • It does not block anything outside a browser: curl, Python, a mobile app and a script on another machine ignore CORS entirely, so an origin allowlist has zero effect on the exposure risks covered earlier.
  • Simple requests still leave your machine: a plain GET to /api/tags needs no preflight, so the browser sends it and only blocks the page from reading the answer, which is a weaker guarantee than most people assume.
  • The default is deliberately local: Ollama already permits origins such as http://localhost and http://127.0.0.1 on any port, plus browser extension origins, which is why a locally served web UI works with no configuration at all.
  • Setting it to * removes the only browser side check you had: the wildcard is the standard fix suggested when a self hosted frontend on a different host cannot connect, and it lets every page in every tab you open reach the API.

If a remote frontend needs access, name its exact origin, for example OLLAMA_ORIGINS=https://chat.example.com, rather than opening it to everything.


Firewall rules or bind address: which layer should you rely on?

Rely on the bind address as the primary control and treat the firewall as the backstop. A bind is a property of the socket, so it cannot be bypassed by a rule someone forgets to reapply. A firewall is a separate ruleset that has to be correct, loaded and evaluated before the traffic reaches the port.

Layer What it genuinely stops Where it lets you down
Bind address (OLLAMA_HOST=127.0.0.1:11434) Every packet from every other machine, on every interface, with no ruleset to maintain Nothing on the local host, and it is one environment variable away from being undone
Host firewall (sudo ufw deny 11434/tcp, or Windows Defender Firewall) Remote access even when the service is bound to 0.0.0.0, and it survives an accidental config change Docker published ports write their own forwarding rules, so a container can be reachable while ufw status looks correct
Router or cloud provider firewall Inbound traffic from the internet, including scans that find port 11434 within hours of exposure Nothing on your LAN, so every other device in the house or office still has full API access
Bind plus host firewall together Both the remote path and a future misconfiguration of either one Neither layer identifies who is calling, so a permitted machine still has unlimited control

Set the bind first, add the deny rule second, then repeat the external test. If your provider gives you a network level firewall, close 11434 there as well and leave it closed permanently.


How do I put a bearer token in front of Ollama with a reverse proxy?

The pattern is always the same: Ollama stays bound to 127.0.0.1, a proxy on the same host owns the public port, and the proxy rejects anything without the right Authorization header. Ollama itself is never modified, and it never learns that authentication exists.

  • Generate a token with real entropy: openssl rand -hex 32 gives you 64 hexadecimal characters, which is far beyond guessable, and it costs nothing compared with a memorable passphrase that a dictionary attack will find.
  • Make the proxy compare, then forward: in Caddy this is a matcher on header Authorization "Bearer <token>" followed by reverse_proxy 127.0.0.1:11434, and in nginx it is an if test on $http_authorization returning 401 before the proxy_pass line.
  • Terminate TLS at the proxy: a bearer token over plain HTTP is readable by anything between the client and the server, so Caddy's automatic certificates or a certbot managed nginx config are part of the control, not an optional extra.
  • Turn off response buffering: nginx needs proxy_buffering off; and a long proxy_read_timeout, otherwise streamed tokens arrive in one block at the end and a slow generation looks like a hung request.
  • Add a rate limit while you are there: capping requests per IP turns a leaked token into a nuisance instead of unlimited free access to your GPU.
  • Check your clients can send headers first: Open WebUI and most OpenAI compatible tools have a field for an API key, while the ollama command line client sets only a host URL, so CLI users need a tunnel rather than a token.

Test with a deliberately wrong token and confirm you get a 401.


When is mutual TLS worth the extra work?

Mutual TLS means the proxy in front of Ollama demands a client certificate before it forwards anything. A bearer token proves knowledge of a string. A client certificate proves possession of a private key that never travels over the wire. That is a real upgrade, and it costs you setup time on every device you own.

  • It is worth it when you have several fixed devices: issue one certificate per machine from your own certificate authority, and you can revoke the stolen laptop alone instead of rotating a shared token and reconfiguring everything else.
  • It is worth it when the endpoint must stay on the public internet: an unauthenticated scanner gets a TLS handshake failure rather than an HTTP 401, so your endpoint never confirms that anything is listening behind it.
  • It is worth it when tokens keep leaking into places you cannot audit: shell history, container environment variables and editor config files all hold plain strings, while a private key can live in the operating system keystore.
  • It is not worth it for a single user on a single laptop: the loopback bind already gives you a stronger boundary than certificates would, with no expiry to manage.
  • It is not worth it when your clients cannot present certificates: many mobile apps and some desktop LLM frontends have no field for a client key, so you would end up running a token path beside the certificate path and inheriting the weaker of the two.

The practical cost is renewal. Certificates expire, commonly at 365 days, and a forgotten renewal takes every client offline at once with an error message that looks nothing like an authentication problem.


SSH tunnels and mesh VPNs: reaching Ollama without opening a port

Both approaches leave the bind on 127.0.0.1 and move the network problem somewhere that already has authentication. Nothing about Ollama changes, and port 11434 stays closed to the world in both cases.

  • The SSH tunnel is the fastest to set up: ssh -N -L 11434:127.0.0.1:11434 user@gpu-box makes the remote API appear on your own loopback, so the ollama command line client, Open WebUI and every default CORS origin work unchanged with no token anywhere.
  • The tunnel inherits SSH's authentication: key based login means no new secret to manage, and closing the terminal closes the access, which is exactly what you want for occasional use and exactly what you do not want for a service that must stay up.
  • A mesh VPN suits always on access: Tailscale or plain WireGuard give the host a private address, in Tailscale's case inside the 100.64.0.0/10 range, reachable only by devices you have enrolled.
  • The mesh handles the network problems for you: it negotiates through NAT over UDP, WireGuard on port 51820 by default and Tailscale on 41641, so there is no router rule to add and no public IP requirement.
  • Access control moves to the mesh, not the API: every enrolled device gets unrestricted use of the model server unless you write ACLs, so treat enrolment as the sensitive act and remove old phones and laptops when you stop using them.
  • Phones and tablets are where the tunnel loses: mobile SSH forwarding is awkward, while a VPN client on the phone makes the endpoint reachable from the sofa with no configuration in the app itself.

How much latency does a proxy add to streamed tokens?

Less than people fear, and in the wrong place. Token generation is bounded by your GPU or CPU, while a local proxy hop is a copy across loopback. What actually ruins the experience is buffering and connection setup, not throughput. Measure it on your own hardware with curl -N -w "%{time_starttransfer}" and compare the direct call against the gated one.

Path to the API Where the extra delay comes from What keeps it small
Direct to 127.0.0.1:11434 Nothing beyond model load and generation, so this is your baseline number Keep a model resident so the first request does not pay the load cost again
Local reverse proxy with a bearer token One extra loopback hop plus a TLS handshake on each new connection Enable HTTP keep alive and reuse connections instead of opening one per request
SSH tunnel to another machine The round trip to that machine, plus SSH encryption on every chunk of the stream Run it over a wired LAN where possible, since the network round trip dominates everything else
Mesh VPN across the internet The full path to the remote host, and a relay hop if a direct connection cannot be negotiated Confirm the connection is direct rather than relayed, because relayed traffic takes a longer route

The number that matters to a reader is time to first token, not tokens per second. Streaming hides steady state overhead well. It hides nothing about a slow start. If a gated setup feels sluggish, check buffering and connection reuse before you blame the encryption.

Top comments (0)