SSD Nodes Learn 🎉 VPS desde $4.99/mes
Guías Matt ConnorPor Matt Connor

Cómo autohospedar LiteLLM como gateway LLM

Configura LiteLLM en un VPS como endpoint compatible con OpenAI, con claves virtuales, presupuestos por clave, fallbacks e imágenes fijadas por versión.

Qué hace un gateway LLM autohospedado

LiteLLM es un gateway LLM de código abierto que aloja usted mismo: un único endpoint HTTP al que llaman todas sus aplicaciones y que reenvía cada petición al proveedor que debe responderla. LLM significa modelo de lenguaje grande. El gateway utiliza la API de completado de chat de OpenAI (interfaz de programación de aplicaciones), por lo que cualquier biblioteca cliente que ya se comunique con OpenAI funciona con él después de cambiar dos valores: la URL base y la clave.

Ese nivel de indirección es el objetivo. Sus aplicaciones dejan de almacenar las credenciales de los proveedores. Cambiar de modelo se reduce a modificar una línea en un archivo de configuración del servidor, en lugar de cambiar el código de cinco servicios. Además, como todas las llamadas pasan por un único proceso, dispone de un lugar donde establecer un presupuesto y conservar un registro de lo gastado.

Una vez en ejecución, dispone de lo siguiente:

  • Un endpoint. Las aplicaciones apuntan a https://gateway.example.com/v1 y solicitan un nombre de modelo que usted ha definido, como bulk o strong.
  • Claves virtuales. Cada aplicación obtiene su propia clave, con su propia lista de modelos permitidos y su propio límite de gasto. Puede revocar una sin afectar a las demás.
  • Alternativas. Una llamada fallida o una petición demasiado grande se reintenta automáticamente con otro modelo.
  • Un registro. Cada petición escribe una fila con su coste, por lo que se puede responder a la pregunta «¿qué aplicación gastó eso?».

Por qué ejecutar el gateway usted mismo

Un router administrado tiene la misma estructura, pero el proceso de otra persona se encuentra en medio de cada solicitud. Si lo ejecuta usted mismo, las claves de su proveedor y el texto de sus prompts permanecen en un equipo que controla. El coste es real: ahora debe operar el componente del que depende cada aplicación. La última sección de esta guía trata ese coste, porque es la parte que la mayoría de los artículos omite.

Qué necesita

  • Un VPS (servidor privado virtual) con Ubuntu 24.04, Docker y el complemento Compose instalados.
  • Un nombre de dominio que apunte al VPS, si máquinas externas accederán a la puerta de enlace mediante TLS (seguridad de la capa de transporte).
  • Al menos una clave de API de un proveedor.

La puerta de enlace no ejecuta inferencias. Reenvía las solicitudes y transmite las respuestas, por lo que su carga de CPU depende del volumen de solicitudes y no del tamaño del modelo. Un servidor con 1 vCPU puede ejecutar varias aplicaciones internas sin problemas. Lo que aumenta es la base de datos, porque la puerta de enlace escribe una fila de gasto por cada solicitud.

Escriba primero config.yaml

El archivo de configuración determina qué modelos puede solicitar un cliente. Hay cuatro secciones de nivel superior relevantes: model_list, litellm_settings, router_settings y general_settings.

model_list:
  - model_name: bulk
    litellm_params:
      model: anthropic/claude-haiku-4-5
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: strong
    litellm_params:
      model: anthropic/claude-sonnet-5
      api_key: os.environ/ANTHROPIC_API_KEY
  - model_name: strong
    litellm_params:
      model: openai/gpt-5.5
      api_key: os.environ/OPENAI_API_KEY

litellm_settings:
  num_retries: 2
  request_timeout: 120
  allowed_fails: 3
  cooldown_time: 30
  json_logs: true
  set_verbose: false

router_settings:
  fallbacks: [{"bulk": ["strong"]}]
  context_window_fallbacks: [{"bulk": ["strong"]}]

general_settings:
  background_health_checks: true
  health_check_interval: 300

model_name es el nombre que envían sus clientes. litellm_params.model es el modelo real, escrito como provider/model. Asigne nombres a los modelos según la tarea, no según el proveedor. Una aplicación que solicita bulk seguirá funcionando si el mes que viene decide que bulk debe ser otro modelo.

api_key: os.environ/ANTHROPIC_API_KEY indica a LiteLLM que lea esa variable en tiempo de ejecución. La clave literal nunca aparece en el archivo, lo que es importante porque config.yaml es el archivo que confirma en el repositorio.

Hay dos entradas con el nombre strong, de forma intencionada. Cuando más de un despliegue tiene el mismo model_name, el enrutador los trata como intercambiables e intenta usar el otro si falla el primero. Así, strong sigue funcionando aunque uno de los proveedores tenga problemas durante un periodo.

num_retries: 2 reintenta el mismo despliegue cuando se produce un error que admite reintentos. Un fallback sólo se activa después de agotar esos reintentos. allowed_fails: 3 con cooldown_time: 30 retira un despliegue de la rotación durante 30 segundos cuando ha fallado 3 veces, de modo que un proveedor que devuelve errores 500 no se intenta en cada solicitud.

fallbacks y context_window_fallbacks tienen activadores diferentes, y el segundo es el útil que muchas personas omiten.

  • fallbacks se activa cuando falla la llamada principal.
  • context_window_fallbacks se activa cuando el proveedor rechaza la solicitud porque supera la ventana de contexto de ese modelo, de modo que una solicitud demasiado grande se envía a un modelo con capacidad suficiente en lugar de devolver un error al cliente.

También existe content_policy_fallbacks, para los casos en que un proveedor rechaza la solicitud por motivos de política de contenido. Configúrelo sólo si dispone de un destino adecuado para esas llamadas.

Implementar LiteLLM en un VPS con Docker Compose

Cree un directorio que contenga tres archivos: config.yaml, docker-compose.yml y .env. El inicio rápido oficial descarga la etiqueta latest. Fije una etiqueta de versión en su lugar. Así, docker compose up -d el mes que viene le proporcionará la misma puerta de enlace que hoy, y una reversión se reduce a una sola línea.

services:
  litellm:
    image: ghcr.io/berriai/litellm:v1.95.0
    restart: unless-stopped
    command: ["--config", "/app/config.yaml", "--num_workers", "1"]
    ports:
      - "127.0.0.1:4000:4000"
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    env_file: .env
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: litellm
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
      POSTGRES_DB: litellm
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U litellm"]
      interval: 5s
      timeout: 5s
      retries: 10
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

Compose lee .env dos veces aquí. Una vez para sustituir ${POSTGRES_PASSWORD} dentro del propio archivo Compose y otra mediante env_file para pasar todas las variables al contenedor.

v1.95.0 era la versión actual en agosto de 2026. Consulte la página de versiones del proyecto y fije la versión que esté vigente cuando realice el despliegue. Cada versión publica una firma, por lo que puede comprobar la imagen antes de confiar en ella:

cosign verify --key https://raw.githubusercontent.com/BerriAI/litellm/v1.95.0/cosign.pub ghcr.io/berriai/litellm:v1.95.0

La línea del puerto es 127.0.0.1:4000:4000, que publica el puerto sólo en la interfaz de loopback. Escriba 4000:4000 en su lugar y la puerta de enlace será accesible desde toda Internet, porque Docker añade sus propias reglas a la cadena FORWARD de iptables y estas se evalúan antes que las de ufw, por lo que ufw deny 4000 no lo detiene. Es la forma más habitual de que una puerta de enlace autohospedada quede expuesta: consulte cómo Docker publica un puerto de contenedor directamente fuera del control de ufw. El tráfico externo llega a través del reverse proxy.

Mantenga las claves de los proveedores fuera de la imagen

El archivo .env contiene todos los secretos. Se proporciona como variable de entorno en tiempo de ejecución, por lo que nunca se incluye en la imagen ni se confirma en el repositorio.

LITELLM_MASTER_KEY=sk-REPLACE_ME
LITELLM_SALT_KEY=sk-REPLACE_ME_TOO
POSTGRES_PASSWORD=REPLACE_ME_AS_WELL
DATABASE_URL=postgresql://litellm:REPLACE_ME_AS_WELL@db:5432/litellm
STORE_MODEL_IN_DB=True
LITELLM_MODE=PRODUCTION
LITELLM_LOG=ERROR
ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-proj-...

Genere las dos claves de LiteLLM con aleatoriedad real y restrinja los permisos del archivo:

printf 'sk-%s\n' "$(openssl rand -hex 32)"
chmod 600 .env

LITELLM_MASTER_KEY es la credencial de administrador. Autentica la API de gestión y es la contraseña de la interfaz de administración en /ui. Ninguna aplicación debe tenerla.

LITELLM_SALT_KEY cifra las credenciales de los proveedores almacenadas en la base de datos. Establézcala una vez y no la cambie. Si la cambia más adelante, las credenciales almacenadas ya no se podrán descifrar. En ese caso, la puerta de enlace se inicia con normalidad, pero todas las llamadas a esos proveedores fallan durante la autenticación.

STORE_MODEL_IN_DB=True permite añadir y editar modelos desde la interfaz de administración sin modificar config.yaml. Es práctico, pero divide la fuente de verdad en dos. Decida cuál es la fuente autoritativa y documente la decisión junto a la configuración.

El motivo para mantener las claves fuera del archivo de configuración es el mismo que para mantenerlas fuera de las herramientas que se entregan a un agente. Mantener los secretos de los proveedores fuera de los agentes de IA explica este patrón, y archivos env y secretos en Docker Compose explica los aspectos operativos.

Inícielo y supervise el primer arranque:

docker compose up -d
docker compose logs -f litellm

Compruebe que realmente funciona

Hay dos comprobaciones sin autenticación y una con autenticación. Fallan por motivos diferentes.

curl -s http://127.0.0.1:4000/health/liveliness
curl -s http://127.0.0.1:4000/health/readiness

/health/liveliness no requiere autenticación y responde con "I'm alive!" mientras el proceso está en ejecución. /health/readiness tampoco requiere autenticación. Devuelve un objeto JSON con "status": "healthy" y un campo db, o devuelve 503 cuando no se puede acceder a la base de datos. Configure la monitorización sobre readiness, porque liveliness permanece en verde en un gateway que no puede resolver ninguna clave virtual.

La comprobación con autenticación es la que se comunica con los proveedores:

curl -s http://127.0.0.1:4000/health \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

Responde con matrices healthy_endpoints y unhealthy_endpoints. Si un modelo aparece en unhealthy_endpoints con un error de autenticación, la clave del proveedor en .env es incorrecta o falta. Ese es el fallo que debe localizar ahora. Como background_health_checks: true está configurado, el proxy ejecuta estas comprobaciones automáticamente cada health_check_interval segundos y /health devuelve el último resultado. Por tanto, consultarlo no envía una solicitud de prueba a los proveedores en cada consulta.

Claves virtuales y presupuestos por clave

Cada aplicación obtiene su propia clave, generada a partir de la clave maestra.

curl -s http://127.0.0.1:4000/key/generate \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "key_alias": "nightly-summariser",
    "models": ["bulk"],
    "max_budget": 5,
    "budget_duration": "30d",
    "rpm_limit": 60,
    "tpm_limit": 200000
  }'

La respuesta contiene un campo key que empieza por sk-. Esa cadena es lo que recibe la aplicación y es lo único que recibe.

  • models es una lista de permitidos de lo que esta clave puede solicitar. La clave anterior puede solicitar bulk y nada más.
  • max_budget: 5 con budget_duration: "30d" son cinco dólares estadounidenses por 30 días consecutivos. Después, la clave deja de funcionar.
  • rpm_limit y tpm_limit limitan las solicitudes por minuto y los tokens por minuto sólo para esta clave.
  • key_alias es lo que reconocerá en el registro de gastos seis semanas después. Establézcalo siempre.

Cuando se agota el presupuesto, la llamada falla con HTTP 401 y un cuerpo de esta forma:

ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07

El código de estado es lo que causa la confusión. Una biblioteca cliente informa de 401 como un problema de autenticación, por lo que el desarrollador que lee el seguimiento de la pila empieza a comprobar si la clave es válida. Registre el cuerpo de la respuesta junto al código de estado. De lo contrario, el agotamiento del presupuesto parecerá siempre una credencial defectuosa.

Inspeccione y ajuste las claves mediante la misma API de gestión:

curl -s "http://127.0.0.1:4000/key/info?key=sk-..." \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

curl -s -X POST http://127.0.0.1:4000/key/update \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"key": "sk-...", "max_budget": 25}'

Un presupuesto aplicado en la puerta de enlace se mantiene incluso cuando el problema está en el propio agente. Por eso es la base del control de costes de los agentes de IA en un VPS.

Enviar trabajo masivo a un modelo económico

Apunte el cliente a la puerta de enlace. URL base, clave y nombre del modelo:

curl -s http://127.0.0.1:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-<the virtual key>" \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "bulk",
    "messages": [{"role": "user", "content": "Say hello in five words."}]
  }'

Cualquier biblioteca cliente de OpenAI funciona igual: establezca base_url en https://gateway.example.com/v1 y api_key en la clave virtual.

La política de enrutamiento de config.yaml se aplica ahora sin que el cliente tenga que conocerla. Una solicitud para bulk se envía al modelo económico. Si esa llamada falla después de sus reintentos, la solicitud se reintenta contra strong. Si el prompt es demasiado largo para bulk, context_window_fallbacks lo envía a strong en lugar de devolver un error. El trabajo masivo, como una pasada de clasificación o la generación de resúmenes de una cola pendiente, se ejecuta con el modelo económico de forma predeterminada. Sólo las solicitudes complejas tienen un coste mayor.

Aquí también se aprecia la utilidad de una puerta de enlace con agentes que usan herramientas. Un servidor MCP (protocolo de contexto del modelo) en el mismo VPS y el agente que lo controla pueden apuntar al mismo endpoint. Así, el modelo que hay detrás puede cambiar sin volver a desplegar ninguno de los dos.

¿Cómo saber si se produjo un fallback?

Este es el modo de fallo que genera costes sin que nada parezca roto. Un fallback correcto devuelve HTTP 200 con un cuerpo de respuesta normal. El modelo económico puede estar caído durante un día y todas las llamadas pueden ser atendidas silenciosamente por el modelo caro. La primera evidencia puede ser la factura.

La evidencia está en las cabeceras de respuesta. Solicítelas:

curl -s -D - -o /dev/null http://127.0.0.1:4000/v1/chat/completions \
  -H "Authorization: Bearer sk-<the virtual key>" \
  -H 'Content-Type: application/json' \
  -d '{"model":"bulk","messages":[{"role":"user","content":"ping"}]}' \
  | grep -i '^x-litellm'
  • x-litellm-model-group indica lo que solicitó el cliente. x-litellm-model-id indica el deployment que respondió. Si no coinciden, se produjo un fallback.
  • x-litellm-attempted-fallbacks y x-litellm-attempted-retries contabilizan los fallos. En una llamada correcta, ambos valores son 0.
  • x-litellm-response-cost indica el coste de esa llamada en dólares estadounidenses.
  • x-litellm-call-id es el identificador que se usa para encontrar la misma llamada en los registros.

Registre x-litellm-attempted-fallbacks en cada solicitud y genere una alerta cuando deje de ser 0. Ese único número marca la diferencia entre una política de enrutamiento que funciona y una política que se ha convertido silenciosamente en «usar siempre el modelo caro».

La versión completa de este enfoque es el tracing, que requiere una configuración propia: Langfuse autoalojado para rastrear llamadas de agentes. LiteLLM incluye el callback, por lo que configurarlo requiere dos líneas y las credenciales.

litellm_settings:
  success_callback: ["langfuse"]
  failure_callback: ["langfuse"]
LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://langfuse.example.com

Configure failure_callback y success_callback. Si omite esta configuración, sólo conservará los traces en los que no se produjo ningún error. Independientemente de esto, LiteLLM escribe una fila de gasto por solicitud en Postgres y la interfaz de administración en /ui lee esa tabla. La tabla crece con el tráfico, así que vigílela si el disco es pequeño.

Pon el gateway detrás de un proxy inverso

Nada fuera del servidor debe poder acceder al puerto 4000. Termina TLS en nginx o Caddy y reenvía las conexiones a la dirección de loopback.

location / {
    proxy_pass http://127.0.0.1:4000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_buffering off;
    proxy_read_timeout 600s;
}

Esas dos líneas suelen omitirse. proxy_buffering off es importante porque una respuesta de streaming consiste en una serie de eventos enviados por el servidor. Si el buffering está habilitado, nginx retiene los fragmentos hasta que termina la respuesta. El cliente permanece sin recibir datos y después obtiene todo de una vez. proxy_read_timeout 600s es importante porque una generación larga supera el valor predeterminado de 60 segundos de nginx. En ese caso, el cliente recibe un 504 y el registro de errores contiene upstream timed out (110: Connection timed out) while reading response header from upstream.

Para el certificado, Certbot con Let's Encrypt en nginx es la opción más directa. Si el servidor ya aloja varios contenedores, Traefik delante de varias aplicaciones de Compose gestiona el enrutamiento y los certificados desde un solo lugar.

La puerta de enlace es ahora un único punto de fallo

Sea claro sobre lo que ha creado. Todas las aplicaciones que administra dependen ahora de un contenedor en un único VPS. Mientras esté detenido, ninguna puede llamar a ningún modelo, incluidos los proveedores que funcionan correctamente. De esto se derivan cuatro consecuencias.

  • Una configuración incorrecta detiene todo de una vez. restart: unless-stopped reinicia un proceso que termina, y vuelve a reiniciar un contenedor que no puede analizar config.yaml, una y otra vez. Lea docker compose logs litellm después de cada cambio de configuración y haga estos cambios cuando tenga tiempo para supervisarlos.
  • Postgres está en la ruta de las peticiones. La búsqueda de claves virtuales y el registro del gasto usan la base de datos. Que /health/readiness devuelva 503 indica que la puerta de enlace está ejecutándose, pero no puede hacer ninguna de las dos operaciones.
  • Escale añadiendo instancias, no haciendo una más grande. La recomendación del propio proyecto es un worker por instancia (--num_workers 1) con varias instancias que compartan una base de datos. Dos puertas de enlace pequeñas detrás de un balanceador de carga eliminan el contenedor único de la arquitectura. No eliminan la base de datos.
  • Haga copias de seguridad de lo que no pueda regenerar. Se trata de config.yaml y .env, junto con un pg_dump de la base de datos. Perder LITELLM_SALT_KEY deja inutilizables las credenciales de proveedores cifradas dentro de ese volcado, por lo que el archivo de entorno y el volcado deben incluirse en el mismo trabajo de copia de seguridad: copias de seguridad de restic en almacenamiento externo.

Actualizar consiste en editar la etiqueta de la imagen y ejecutar docker compose up -d. LiteLLM ejecuta prisma migrate deploy al iniciar de forma predeterminada, por lo que el contenedor nuevo migra el esquema de la base de datos durante su primer arranque. Haga el volcado antes de cambiar la etiqueta, porque restaurar la imagen anterior no deshace una migración que ya se haya ejecutado.

FAQ

¿LiteLLM añade una latencia apreciable a cada llamada?

El proyecto publica 8 ms en el percentil 95 con 1000 solicitudes por segundo, según su README de agosto de 2026. Considere esa cifra como un dato del proveedor. El factor que realmente modifica la latencia es la distancia de red entre sus aplicaciones y la gateway, porque ha añadido un viaje de ida y vuelta a cada llamada. Ejecute la gateway en la misma región que las aplicaciones que la utilizan y mida después su propia sobrecarga con la cabecera x-litellm-overhead-duration-ms en una respuesta real.

¿Por qué dejó de funcionar el streaming después de poner nginx delante?

Porque nginx almacena en búfer las respuestas del upstream de forma predeterminada y una respuesta de streaming es una serie de eventos enviados por el servidor. Con proxy_buffering activado, nginx recopila los fragmentos y los libera sólo cuando termina la respuesta. El cliente permanece sin recibir datos y después obtiene toda la respuesta de una vez. Establezca proxy_buffering off; en el bloque location. Aumente proxy_read_timeout en el mismo bloque, porque una generación larga puede superar de lo contrario el valor predeterminado de nginx, 60 segundos, y el cliente recibe un 504.

¿Qué ocurre cuando una clave virtual agota el presupuesto?

La llamada falla con HTTP 401 y un cuerpo con el formato ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07. El 401 puede inducir a error: una biblioteca cliente lo informa como un fallo de autenticación, por lo que se empieza a comprobar si la clave es válida en lugar de leer el mensaje. Registre el cuerpo de la respuesta junto con el código de estado. Confirme la posición real de la clave con /key/info?key=sk-... frente a la clave maestra y aumente el límite con /key/update si el presupuesto era demasiado bajo.

¿Puede la gateway encaminar solicitudes a un modelo local además de modelos alojados?

Sí. Es otra entrada en model_list. Use el prefijo ollama_chat/ con un api_base, por ejemplo model: ollama_chat/llama3.1 junto con api_base: http://ollama:11434. Desde dentro de un contenedor, localhost hace referencia a ese contenedor. Use el nombre del servicio de Compose o la dirección del host en la red de Docker, nunca 127.0.0.1. Poner en ejecución el modelo local es una tarea independiente. Consulte alojar un LLM propio con Ollama en un VPS.