Ollama no tiene contraseña: cómo proteger la API
Ollama no incluye autenticación: quien alcance el puerto 11434 puede ejecutar, descargar y borrar modelos. Aplica estas tres correcciones, en orden.
La API de Ollama no tiene contraseña
La API de Ollama no tiene autenticación. El servidor que ejecuta no tiene usuario, contraseña, comprobación de clave ni lista de permitidos. Cualquier sistema que pueda abrir una conexión TCP al puerto 11434 puede enumerar sus modelos, ejecutarlos, descargar otros y eliminar los que ya tiene.
La documentación oficial lo indica claramente: «No se requiere autenticación al acceder localmente a la API de Ollama mediante http://localhost:11434». La palabra localmente define todo el modelo de seguridad. Ollama se enlaza a 127.0.0.1 de forma predeterminada, por lo que, en un portátil, la interfaz de loopback controla el acceso. Si mueve ese listener a una dirección pública, el control de acceso desaparece porque no hay ningún mecanismo que lo sustituya.
Por eso esto es importante en un VPS (servidor privado virtual). La configuración predeterminada es segura. El primer cambio que suele hacerse, abrir el listener para que otra máquina pueda usar el modelo, es el cambio que elimina todas las protecciones de una vez.
Lo que revela un puerto 11434 abierto
Todos los endpoints. No hay un modo de solo lectura ni un puerto de administración independiente. Estas son las peticiones reales, dirigidas a la dirección del servidor en lugar de 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"}'En términos operativos, ocurren cuatro problemas:
- Tu CPU o GPU ejecuta inferencias para otra persona. En un plan con una cuota de CPU de uso razonable, una carga sostenida consume tu cuota por cuenta de un desconocido, y mantener bajo control los costes de las cargas de trabajo de IA en un VPS resulta mucho más difícil cuando no eres el único cliente.
/api/pullescribe en tu disco. Cada modelo ocupa entre dos y cuarenta gigabytes. Una secuencia de descargas llena el volumen, y un disco lleno interrumpe todos los demás servicios del servidor, no solo Ollama.- Las peticiones llegan a tu proceso y se registran. Con el nivel de registro predeterminado, Ollama solo registra metadatos: el endpoint, el estado, la latencia y la dirección del cliente, pero no el texto del prompt. Aun así, queda un registro de quién usó tu servidor y para qué, almacenado en tu journal, sin que tú hayas elegido recopilarlo.
/api/deleteelimina modelos. Para recuperarlos, hay que volver a descargarlos usando tu propio ancho de banda.
Nada de esto requiere un exploit. Es la API documentada funcionando exactamente según su diseño.
La clave Ed25519 no controla el acceso
Si busca «Ollama API key», encontrará dos elementos diferentes. Ninguno es una contraseña para su servidor. Distinguirlos elimina la mayor parte de la confusión.
El primero es el par de claves de identidad. Ollama genera un par de claves Ed25519 durante la primera ejecución. En Linux, el script de instalación crea un usuario del sistema llamado ollama, con el directorio personal en /usr/share/ollama. Por tanto, el par se encuentra aquí:
/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pubEsta clave se utiliza para las conexiones salientes. ollama signin registra la parte pública en su cuenta de ollama.com. También autoriza el envío de un modelo al registro o la descarga de uno privado. Identifica su máquina ante ollama.com. No solicita nada a los clientes que se conectan a su máquina. Eliminarla, rotarla o no crearla nunca no cambia quién puede llamar a su API.
El segundo es OLLAMA_API_KEY. Esa variable contiene una clave que crea en https://ollama.com/settings/keys. Su cliente la envía como Authorization: Bearer $OLLAMA_API_KEY al llamar a la API alojada en https://ollama.com/api. Es una credencial para ese servicio, que usted utiliza como cliente. Su propio ollama serve nunca la lee. Establecer OLLAMA_API_KEY en su VPS no añade una contraseña a su VPS.
Por tanto, no hay ninguna opción que activar. Las tres defensas siguientes funcionan de la misma manera: mantenga el puerto inaccesible y coloque delante un componente que sí compruebe el acceso.
Compruebe en qué direcciones está escuchando actualmente el servidor
sudo ss -tlnp | grep 11434El resultado seguro muestra la dirección de loopback:
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))El resultado expuesto muestra todas las interfaces:
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))0.0.0.0 significa todas las direcciones IPv4 del equipo, incluida la pública. *:11434 y [::]:11434 significan lo mismo, pero también incluyen IPv6.
Ahora confirme el resultado desde fuera. Ejecute esto en su portátil, no en el servidor:
curl -m 5 http://YOUR_SERVER_IP:11434/api/versioncurl: (28) Connection timed out after 5001 milliseconds es el resultado esperado, al igual que curl: (7) Failed to connect ... Connection refused. Un objeto JSON que incluya un campo version significa que cualquier persona puede acceder a toda la API. Probar con curl en el propio servidor no demuestra nada, porque loopback siempre responde.
La exposición suele producirse de una de estas dos formas. La primera es un cambio deliberado, porque alguien necesitaba que otro equipo accediera al modelo:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"Esa única línea expone todo el servicio. La segunda forma es Docker, y no requiere editar nada. Tiene su propia sección más abajo.
Defensa 1: mantenerlo en localhost y acceder mediante un túnel
Empiece por esta opción. No requiere software nuevo ni crea credenciales que puedan filtrarse. El puerto nunca existe en una interfaz pública, por lo que los escaneos no pueden encontrarlo.
Establezca explícitamente la dirección de escucha en lugar de depender del valor predeterminado:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"Esto escribe /etc/systemd/system/ollama.service.d/override.conf. Aplíquelo y compruebe el resultado:
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434ss debería mostrar ahora 127.0.0.1:11434. Si sigue mostrando 0.0.0.0, otro archivo drop-in tiene prioridad. Ejecute systemctl cat ollama.service para mostrar la unidad y todos los drop-in con sus rutas, y elimine el archivo obsoleto.
Para usar el modelo desde su portátil, reenvíe el puerto mediante SSH:
ssh -N -L 11434:127.0.0.1:11434 you@your-server-L 11434:127.0.0.1:11434 abre el puerto 11434 en su portátil y envía todo lo que recibe allí a 127.0.0.1:11434, visto desde el servidor. -N indica a SSH que no ejecute ningún comando remoto, por lo que el proceso mantiene abierto el túnel. Mientras esté en ejecución, esto funciona en su portátil:
curl -s http://localhost:11434/api/tagsEncontrará dos fallos habituales. bind [127.0.0.1]:11434: Address already in use significa que su portátil está ejecutando su propio Ollama en ese puerto. Elija otro puerto local con -L 11500:127.0.0.1:11434 y configure el cliente para usar 11500. Una respuesta vacía a través de un túnel que se conectó correctamente significa que SSH funciona, pero Ollama no está escuchando en el lado del servidor. Compruebe allí ss antes de modificar el comando SSH.
Para varias máquinas cliente, una red privada es mejor que crear un túnel por persona. Conecte las máquinas mediante WireGuard o Tailscale y, a continuación, haga que Ollama escuche en su dirección de esa red en lugar de 0.0.0.0:
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"El puerto sólo existe entonces en una interfaz a la que es necesario acceder con una clave. Esto también evita que un error del firewall lo exponga: aunque una regla permita accidentalmente el acceso desde cualquier origen, no puede exponer un servicio de escucha que la interfaz pública no tenga.
Defensa 2: un proxy inverso que comprueba un token bearer
Cuando algo en Internet público tenga que llamar al modelo, mantenga Ollama en loopback y coloque un proxy delante. El proxy termina TLS (seguridad de la capa de transporte) y rechaza las solicitudes que no incluyan la cabecera correcta. Ollama sigue aceptando conexiones sólo desde 127.0.0.1, por lo que el proxy es la única ruta de acceso.
Genere primero un token real. No lo invente manualmente:
openssl rand -base64 36Un sitio de nginx que lo comprueba:
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;
}
}Las cinco líneas hacen trabajo real. Cada una evita un fallo que, de otro modo, encontraría.
if dentro de un bloque location normalmente es una mala idea en nginx, pero un cuerpo de exactamente return es una de las dos formas que se comportan de manera predecible, por lo que este uso es seguro.
location = /api/pull es una coincidencia exacta. nginx da prioridad a las coincidencias exactas sobre el prefijo location /, por lo que esos tres endpoints se rechazan antes de comprobar el token. Un token válido permite usar la inferencia, pero no llenar el disco.
proxy_set_header Host 127.0.0.1:11434; es importante porque Ollama inspecciona las cabeceras Host y Origin entrantes. Pasar directamente el nombre de host público del proxy puede producir un 403 Forbidden procedente de Ollama y no de nginx, lo que dificulta la depuración. OLLAMA_ORIGINS es el otro mecanismo, para un cliente de navegador que necesite permitir un origen concreto.
proxy_buffering off; es importante porque Ollama transmite la respuesta token a token. Con el almacenamiento en búfer activado, nginx retiene la transmisión y la entrega completa al final, por lo que el cliente parece bloqueado durante toda la generación.
proxy_read_timeout 600s; es importante porque nginx usa 60 segundos de forma predeterminada. Una generación larga en la CPU supera ese tiempo fácilmente, el cliente recibe 504 Gateway Time-out y /var/log/nginx/error.log registra upstream timed out (110: Connection timed out) while reading response header from upstream. La solicitud seguía ejecutándose. nginx la abandonó.
Recargue la configuración y pruebe ambas rutas:
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/tagsLa primera debería mostrar 401. La segunda debería mostrar la lista de modelos. Si la primera también devuelve la lista de modelos, el bloque map está en el ámbito incorrecto. Debe estar en el nivel http, así que colóquelo en un archivo bajo /etc/nginx/conf.d/ o por encima del bloque server, nunca dentro de server.
Caddy hace el mismo trabajo con autenticación básica en cuatro líneas. Esto se adapta mejor a un cliente de navegador que un token bearer:
llm.example.com {
basic_auth {
apiuser PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:11434
}Ejecute caddy hash-password para generar el hash bcrypt que espera. Hay una diferencia de nombres importante: la directiva era basicauth antes de Caddy v2.8 y ahora es basic_auth. Por eso, una configuración copiada de una guía antigua no se carga y Caddy indica la directiva que no reconoce.
Independientemente del proxy que elija, se trata de un único secreto compartido por todos. Todos los clientes que lo posean tienen el mismo acceso. Revocarlo implica editar la configuración y actualizar todos los clientes al mismo tiempo.
Defensa 3: una puerta de enlace que emite claves por cliente
Cuando más de una persona o aplicación llama al modelo, un token compartido deja de ser suficiente. No puede saber qué cliente provocó la carga ni bloquear a uno sin bloquearlos a todos. Una puerta de enlace ocupa el lugar del proxy, utiliza la misma API compatible con OpenAI, emite una clave independiente para cada cliente y registra el uso de cada clave. Una puerta de enlace LiteLLM autohospedada es la opción habitual. Además del control de acceso, añade presupuestos por clave y registros de solicitudes.
La regla de la defensa 1 no cambia. Ollama se enlaza a 127.0.0.1, la puerta de enlace es el único proceso que se comunica con él y también es el único servicio con un listener público. Una puerta de enlace en un equipo donde el puerto 11434 sigue abierto a Internet es sólo un adorno, porque los clientes pueden evitarla directamente.
La trampa del firewall: un puerto de contenedor publicado omite UFW
Por eso existen instancias expuestas en servidores cuyos propietarios configuraron correctamente el firewall.
UFW (uncomplicated firewall) escribe sus reglas en la cadena INPUT de la tabla filter del kernel, y INPUT gestiona los paquetes dirigidos al propio host. La opción -p de Docker escribe una regla de NAT de destino (network address translation) en la cadena PREROUTING de la tabla nat, que el kernel evalúa antes de decidir adónde se dirige el paquete. Cuando se produce la decisión de enrutamiento, el destino ya se ha reescrito a la dirección del contenedor, por lo que el paquete se reenvía en lugar de entregarse localmente y atraviesa FORWARD en vez de INPUT. Las reglas INPUT de UFW nunca se consultan, por lo que el paquete rodea el firewall en lugar de atravesarlo.
Por eso esta secuencia deja el puerto 11434 abierto a Internet:
sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollamay sudo ufw status sigue indicando que el firewall está activo con una política predeterminada de denegación. Ambas lecturas son correctas al mismo tiempo, y precisamente por eso se confía en la lectura equivocada. Puede ver la regla que lo provoca:
sudo iptables -t nat -L DOCKER -nLa corrección consiste en añadir una dirección en la opción de publicación:
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 es una abreviatura de -p 0.0.0.0:11434:11434. Especificar 127.0.0.1 vincula el lado del host de la asignación a loopback, de modo que el túnel SSH y el reverse proxy sigan accediendo a él, pero Internet no pueda hacerlo. Recrear el contenedor es seguro en este caso porque los modelos están en el volumen con nombre ollama, no dentro del contenedor.
Confirme que ambas vistas coinciden:
docker port ollama
sudo ss -tlnp | grep 11434docker port ollama debería mostrar 11434/tcp -> 127.0.0.1:11434. Si muestra 0.0.0.0:11434, el servicio sigue expuesto. Una vez entendido el mecanismo, se aplica a todos los contenedores que publique: por qué los puertos publicados por Docker omiten UFW explica la cadena DOCKER-USER y las reglas que permanecen después de reiniciar Docker. Si todavía está configurando la política del propio host, las reglas de UFW que necesita un VPS nuevo explica la base sobre la que se apoya.
Quién ejecuta el proceso
El script de instalación de Linux crea una cuenta dedicada y ejecuta el servicio con esa cuenta:
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaLa unidad ubicada en /etc/systemd/system/ollama.service establece User=ollama y Group=ollama. No cambie esos valores. Un ollama serve rápido iniciado manualmente en un terminal se ejecuta con la cuenta del usuario que inició sesión. Si esa cuenta es root, una API sin autenticación escribe archivos como root. Compruebe cuál es la situación:
ps -o user= -C ollamaEl resultado debe ser ollama. Cualquier otro resultado indica que hay un proceso iniciado manualmente ejecutándose junto con la unidad o en lugar de ella. El mismo criterio se aplica a cada daemon que añada posteriormente, y ejecutar servicios con cuentas con privilegios mínimos permite hacerlo correctamente.
Cómo comprobar que el endpoint de la API de Ollama es seguro
Independientemente de la opción que haya elegido, una prueba lo confirma y debe ejecutarse desde otra máquina:
curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tagsAmbas solicitudes deben agotar el tiempo de espera o ser rechazadas. Si ha configurado un proxy, las mismas dos rutas en el nombre de host del proxy deben devolver 401 sin credenciales y JSON válido con ellas.
A continuación, lea una vez el registro de acceso. Así sabrá si alguien encontró el puerto mientras estuvo expuesto:
journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1Ollama escribe una línea por solicitud e incluye la dirección del cliente:
[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"Todas las líneas deben mostrar 127.0.0.1 una vez que Ollama esté enlazado a loopback, porque esa es la única dirección desde la que puede llegar una conexión. Una dirección pública en esa columna indica una solicitud externa y la marca de tiempo muestra cuándo ocurrió. El resultado deseado es que ese comando no produzca ninguna salida. Si esta parte del uso de modelos es nueva para usted, ejecutar Ollama en un VPS explica la instalación, el dimensionamiento del modelo y los límites de memoria que determinan qué se cargará realmente.
FAQ
¿Ollama tiene una clave de API o una contraseña?
No. El servidor que ejecuta no tiene ningún tipo de autenticación, y la documentación oficial indica que no se requiere autenticación para acceder a la API. Los dos elementos denominados «Ollama API key» se usan para otra cosa. El par Ed25519 de /usr/share/ollama/.ollama/ demuestra la identidad de su máquina ante ollama.com para que pueda insertar modelos y descargar modelos privados. OLLAMA_API_KEY es una credencial que el cliente envía a la API alojada en https://ollama.com/api. Su propio ollama serve no lee ninguno de los dos, por lo que el control de acceso debe proceder de la red o de un proxy situado delante.
¿Es seguro OLLAMA_HOST=0.0.0.0 si tengo un firewall?
Sólo mientras ningún otro componente escriba reglas de firewall en ese equipo. 0.0.0.0 significa que el listener existe realmente en la interfaz pública y que confía únicamente en el firewall para mantenerlo inaccesible. Esa confianza se rompe en cuanto Docker publica un puerto, porque la regla DNAT que Docker añade a la tabla nat se evalúa antes de que el paquete llegue a la cadena INPUT donde reside UFW. Por tanto, el paquete se reenvía y UFW nunca lo ve. Enlazar con 127.0.0.1 o con la dirección de un túnel privado elimina el listener de la interfaz pública. Así, un error del firewall ya no tiene nada que exponer.
¿Cómo compruebo si mi puerto de Ollama está abierto a Internet?
Ejecute sudo ss -tlnp | grep 11434 en el servidor y curl -m 5 http://YOUR_SERVER_IP:11434/api/version desde otra máquina. ss mostrando 127.0.0.1:11434 y el tiempo de espera agotado de curl remoto son las dos respuestas que necesita. Si ss muestra 0.0.0.0:11434 o *:11434 mientras curl remoto devuelve JSON, significa que la API completa es accesible. No haga la prueba con curl en el propio servidor, porque loopback responde independientemente de la dirección de enlace configurada.
¿Puedo cambiar el puerto de 11434 a uno aleatorio?
No, y conviene explicar el motivo. Un puerto diferente sólo ralentiza el análisis de un único puerto. Los escáneres recorren todo el rango, y una petición a /api/tags identifica el servicio, independientemente del puerto al que haya llegado. Cambiar el puerto también rompe los valores predeterminados de todos los clientes y dificulta entender su propia configuración más adelante. Enlace con loopback. Así elimina el listener en lugar de trasladarlo.
Alguien accedió a mi instancia de Ollama abierta. ¿Qué debo comprobar?
Enlácela con 127.0.0.1 y reinicie primero el servicio para detener la exposición antes de comenzar la investigación. Después, ejecute journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 para ver qué direcciones externas llamaron a qué endpoints y cuándo. Compare ollama list con los modelos que esperaba tener, ya que /api/pull no requiere autenticación y un modelo que no descargó representa tanto uso de disco como una evidencia. Compruebe el espacio libre con df -h. Ollama no registra el texto de las solicitudes en el nivel de registro predeterminado. Por tanto, tendrá un registro de quién hizo la petición y para qué modelo, pero no de lo que se generó.