Ollama detrás de un proxy inverso con Caddy o Nginx para transmisión HTTPS

HTTPS para Ollama sin interrumpir las respuestas en streaming.

Índice

Ejecutar Ollama detrás de un proxy inverso es la forma más sencilla de obtener HTTPS, control de acceso opcional y un comportamiento de transmisión predecible.

Este artículo se centra en la entrada (ingress) de Caddy y Nginx para la API de Ollama, no en el código del cliente.

ollama behind proxy

Si ya tienes clientes en Python o Go que se comunican con Ollama, este artículo es la pieza que faltaba: la entrada y el transporte para la misma API.

Para saber cómo encaja Ollama junto a vLLM, Docker Model Runner, LocalAI y las compensaciones del alojamiento en la nube, consulta LLM Hosting in 2026: Local, Self-Hosted & Cloud Infrastructure Compared.

Para ejemplos de solicitudes y código de cliente, consulta Ollama CLI Cheatsheet.

Para capas de interfaz de usuario y multiusuario, consulta Open WebUI overview, quickstart and alternatives.

Para una visión más amplia sobre el autoalojamiento y el control de datos, consulta LLM self-hosting and AI sovereignty.

Para un servicio de Ollama de nodo único reproducible en Docker Compose (volumenes persistentes, OLLAMA_HOST, GPUs NVIDIA, actualizaciones), consulta Ollama in Docker Compose with GPU and Persistent Model Storage.

Por qué debes usar un proxy para Ollama en lugar de exponer el puerto 11434

Ollama está diseñado para ejecutarse primero de forma local. De fábrica, se vincula al localhost en el puerto 11434, lo cual es ideal para una estación de trabajo de desarrollo y es una pista nada sutil de que el puerto bruto no está destinado a estar expuesto a internet.

Considero el puerto 11434 como una API interna de alto costo. Si es accesible desde internet público, cualquiera que la encuentre puede consumir tu tiempo de CPU o GPU, llenar tu disco descargando modelos o simplemente mantener las conexiones abiertas hasta que algo falle por tiempo de espera. Un proxy inverso no hace que Ollama sea más seguro por arte de magia, pero te ofrece un lugar para colocar los controles que importan en el borde: TLS, autenticación, tiempos de espera, límites de tasa y registros.

Esto importa porque la API local de Ollama no viene con una capa de autenticación integrada. Si la expones, típicamente añades autenticación en el borde o la mantienes privada y accesible solo a través de una red de confianza.

La segunda razón es la experiencia de usuario (UX). Ollama transmite respuestas por defecto. Si el proxy realiza un búfer o comprime en el lugar equivocado, la transmisión se siente rota y las interfaces de usuario parecen estar “pensando” sin generar salida.

Arquitectura mínima y estrategia de vinculación

Una arquitectura mínima limpia se ve así:

Cliente (curl, Python, Go, UI)
        |
        | HTTPS (Autenticación básica opcional o SSO)
        v
Proxy inverso (Caddy o Nginx)
        |
        | HTTP (LAN privada, localhost o red de Docker)
        v
Servidor Ollama (ollama serve en 127.0.0.1:11434)

Dos reglas prácticas mantienen esto aburrido de la mejor manera.

Primero, mantén Ollama privado y mueve la exposición al proxy. Si Caddy o Nginx se ejecutan en el mismo host, haz proxy a 127.0.0.1:11434 y no cambies la dirección de vinculación de Ollama. Si el proxy se ejecuta en otro lugar (host separado, VM separada o red de contenedores), vincula Ollama a una interfaz privada, no a 0.0.0.0 en la NIC pública, y confía en un firewall.

Segundo, decide temprano si los navegadores llamarán directamente a Ollama. Si una herramienta basada en navegador accede a Ollama desde un origen diferente, es posible que tengas que lidiar con CORS. Si todo se sirve desde un dominio a través del proxy (recomendado para la cordura mental), a menudo puedes evitar CORS por completo y mantener Ollama estricto.

Configuraciones de proxy inverso para transmisión y WebSockets

La API de Ollama es HTTP estándar y su transmisión es JSON delimitado por líneas nuevas (NDJSON). Eso significa que quieres un proxy que pueda hacer tres cosas bien:

  • No realizar búfer de las respuestas de transmisión.
  • No matar solicitudes de larga duración solo porque el modelo tardó un poco en hablar.
  • Si una UI utiliza WebSockets (algunas lo hacen), reenvía Upgrade de forma limpia.

Puedes mantener esto simple. En muchos casos, “manejo correcto de WebSockets” es simplemente tener una configuración que sea segura para Upgrade, incluso si el upstream no utiliza WebSockets hoy en día.

Ejemplo de Caddyfile para Caddy

Caddy es la opción de “menos configuración, más valores predeterminados”. Si pones un nombre de dominio público en la dirección del sitio, Caddy obtendrá y renovará los certificados automáticamente.

Configuración mínima de proxy inverso, HTTPS y configuraciones amigables para la transmisión:

# ollama.example.com A/AAAA -> tu host proxy
ollama.example.com {

    # Autenticación básica opcional en el borde.
    # Genera un hash de contraseña con:
    #   caddy hash-password --algorithm bcrypt
    #
    # basic_auth {
    #   alice $2a$12$REDACTED...
    # }

    reverse_proxy 127.0.0.1:11434 {

        # Algunas configuraciones prefieren fijar el encabezado Host upstream.
        # Los propios documentos de Ollama muestran este patrón para Nginx.
        header_up Host localhost:11434

        # Para cargas de trabajo de transmisión o tipo chat, prefieres baja latencia.
        # La transmisión NDJSON generalmente vacía el búfer inmediatamente de todas formas, pero esto lo hace explícito.
        flush_interval -1

        transport http {
            # Evita la negociación de gzip upstream si interfiere con la transmisión.
            compression off

            # Dale tiempo a Ollama para cargar un modelo y producir el primer fragmento.
            response_header_timeout 10m
            dial_timeout 10s
        }
    }
}

Si ya tienes una puerta de enlace SSO (oauth2-proxy, Authelia, outpost de authentik, etc.), Caddy tiene una directiva de autenticación hacia adelante opinada. El patrón es “autenticación primero, luego proxy”:

ollama.example.com {
    forward_auth 127.0.0.1:4180 {
        uri /oauth2/auth
        # Copia los encabezados de identidad que devuelve tu puerta de enlace, si los necesitas.
        copy_headers X-Auth-Request-User X-Auth-Request-Email Authorization
    }

    reverse_proxy 127.0.0.1:11434
}

Ejemplo de bloque de servidor para Nginx

Nginx te da un poco más de libertad. La ventaja es que los controles son explícitos y tiene primitivas integradas para limitar la tasa y limitar las conexiones. La trampa es el búfer: Nginx realiza búfer de las respuestas proxeadas por defecto, que es lo opuesto de lo que quieres para la transmisión NDJSON.

Este ejemplo incluye:

  • Redirección de HTTP a HTTPS
  • Rutas de certificados TLS (estilo Certbot)
  • Reenvío seguro de Upgrade para WebSocket
  • proxy_buffering off para transmisión amigable
  • Tiempos de espera más largos que el valor predeterminado de 60s
# /etc/nginx/conf.d/ollama.conf

# Manejo seguro de encabezado de conexión para WebSocket
map $http_upgrade $connection_upgrade {
    default upgrade;
    ""      close;
}

# Limitación de tasa de solicitudes opcional (basada en IP)
# limit_req_zone $binary_remote_addr zone=ollama_rate:10m rate=10r/s;

server {
    listen 80;
    server_name ollama.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ollama.example.com;

    ssl_certificate     /etc/letsencrypt/live/ollama.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ollama.example.com/privkey.pem;

    # Autenticación básica opcional en el borde.
    # auth_basic "Ollama";
    # auth_basic_user_file /etc/nginx/.htpasswd;

    location / {
        # Limitación de tasa opcional
        # limit_req zone=ollama_rate burst=20 nodelay;

        proxy_pass http://127.0.0.1:11434;

        # Coincide con el patrón de documentación de Ollama al hacer proxy a localhost.
        proxy_set_header Host localhost:11434;

        # Manejo de Upgrade de WebSocket (inofensivo si no se usa).
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Crítico para transmisión NDJSON.
        proxy_buffering off;

        # Evita tiempos de espera de 60s mientras se esperan tokens.
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Si quieres una puerta de estilo SSO en Nginx, el patrón equivalente es auth_request. Nginx envía una sub-solicitud a tu servicio de autenticación y solo hace proxy a Ollama cuando la autenticación devuelve 2xx.

Automación de TLS y problemas de renovación

Para TLS, la división operativa es simple.

Con Caddy, TLS suele ser “parte del proxy inverso”. HTTPS automático es una de sus funciones insignia, por lo que la emisión y renovación de certificados están acopladas a mantener Caddy en ejecución, tener DNS funcional y exponer los puertos 80 y 443.

Con Nginx, TLS suele ser “un cliente ACME separado más Nginx”. El modo de fallo común no es la criptografía, es la tubería:

  • Puerto 80 no alcanzable para desafíos HTTP-01.
  • Certificados almacenados en un contenedor pero no persistentes.
  • Límites de tasa al hacer instalaciones nuevas repetidas o despliegues de prueba.

Un punto sutil que importa para servicios de larga vida es que la vida útil de los certificados es corta por diseño. Trata las renovaciones como un requisito de automatización de fondo, no como un evento anual en el calendario.

Autenticación, control de abuso y verificación

Esta es la parte que hace que un endpoint LLM expuesto a internet se sienta profesional.

Opciones de autenticación, desde lo brusco hasta lo elegante

La autenticación básica en el proxy es brusca, pero sorprendentemente efectiva para un endpoint privado. También es fácil de aplicar tanto a solicitudes HTTP como a actualizaciones de WebSocket.

Si quieres flujos de inicio de sesión amigables para el navegador, forward auth y auth_request son el patrón común. Tu proxy se mantiene sin estado y una puerta de autenticación posee las sesiones y MFA. La compensación es tener más partes móviles.

Si ya estás ejecutando Open WebUI, también puedes confiar en su autenticación a nivel de aplicación y mantener Ollama mismo privado. El proxy entonces protege Open WebUI, no directamente a Ollama.

Si no necesitas acceso público en absoluto, un enfoque solo de red puede ser más limpio. Por ejemplo, Tailscale Serve puede exponer un servicio local dentro de tu tailnet sin abrir puertos de entrada en tu router.

Básicos de abuso para una API costosa

Ollama es una API local poderosa y su superficie va más allá de la generación. Tiene endpoints para chat, incrustaciones, listado de modelos y comprobaciones de versión. Trata toda la API como sensible.

Referencia oficial de la API (endpoints y transmisión): https://docs.ollama.com/api

En la capa del proxy, hay tres controles de bajo esfuerzo que reducen el dolor del primer día:

  • Limitación de tasa por IP en endpoints de generación.
  • Límites de conexión para detener que un pequeño número de clientes mantengan todo abierto.
  • Tiempos de espera conservadores que coincidan con tu modelo y realidad de hardware, no con valores predeterminados web genéricos.

En la capa de Ollama, también puede rechazar la sobrecarga con 503 y tiene controles del lado del servidor para la cola. La limitación de tasa del proxy te evita llegar allí tan a menudo.

Lista de verificación

Usa las mismas comprobaciones que usarías para cualquier API de transmisión.

  1. Conectividad básica y TLS

    • curl -sS https://ollama.example.com/api/version
    • curl -sS https://ollama.example.com/api/tags | head
  2. La transmisión funciona de extremo a extremo (sin búfer)

    • curl -N https://ollama.example.com/api/generate -H "Content-Type: application/json" -d '{"model":"mistral","prompt":"Write 10 words only.","stream":true}'

    Si estás detrás de Autenticación Básica:

    • curl -N -u alice:REDACTED https://ollama.example.com/api/generate -H "Content-Type: application/json" -d '{"model":"mistral","prompt":"Write 10 words only.","stream":true}'
  3. Cordura de la UI del navegador

    • Carga tu interfaz de chat y activa una respuesta.
    • Si la UI utiliza WebSockets, confirma que no ves errores 400 o 426 y que la conexión se mantiene abierta durante la generación.

Si la salida de curl solo aparece al final, casi siempre es por el búfer en el proxy. Vuelve a comprobar proxy_buffering off en Nginx y considera forzar el vaciado de baja latencia en Caddy para el bloque de sitio de Ollama.