SSD Nodes Learn 🎉 VPS dès $4.99/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-07

Auto-héberger LiteLLM comme passerelle LLM

Découvrez comment exposer un endpoint OpenAI-compatible avec LiteLLM sur un VPS, des clés virtuelles, des budgets par clé, des fallbacks et des images épinglées.

Ce que fait une passerelle LLM auto-hébergée

LiteLLM est une passerelle LLM open source que vous hébergez vous-même : un endpoint HTTP unique que toutes vos applications utilisent, puis qui transmet chaque requête au fournisseur chargé d’y répondre. LLM signifie « grand modèle de langage ». La passerelle utilise l’API de chat completions d’OpenAI (interface de programmation applicative). Toute bibliothèque cliente qui communique déjà avec OpenAI fonctionne donc avec elle après deux modifications : l’URL de base et la clé.

Cette couche d’indirection est l’objectif principal. Vos applications ne contiennent plus les identifiants des fournisseurs. Pour changer de modèle, une seule ligne du fichier de configuration sur le serveur suffit, au lieu de modifier le code de cinq services. Comme chaque appel passe par un seul processus, vous disposez aussi d’un emplacement pour définir un budget et conserver un relevé des dépenses.

Une fois le service démarré, vous disposez des éléments suivants :

  • Un endpoint unique. Les applications utilisent https://gateway.example.com/v1 et demandent un nom de modèle que vous avez choisi, comme bulk ou strong.
  • Des clés virtuelles. Chaque application reçoit sa propre clé, avec sa propre liste de modèles autorisés et son propre plafond de dépenses. Vous pouvez en révoquer une sans modifier les autres.
  • Des solutions de repli. Un appel en échec ou un prompt trop volumineux est automatiquement relancé avec un autre modèle.
  • Un relevé journalisé. Chaque requête écrit une ligne contenant son coût. Vous pouvez donc savoir quelle application a engagé chaque dépense.

Pourquoi exécuter vous-même la gateway

Un routeur managé a la même architecture, mais le processus d’un tiers s’interpose dans chaque requête. En l’exécutant vous-même, vous conservez les clés de votre fournisseur et le texte de vos prompts sur une machine que vous contrôlez. Le coût est réel : vous devez désormais exploiter le composant dont dépend chaque application. La dernière section de ce guide traite de ce coût, car c’est généralement la partie omise par les autres guides.

Ce dont vous avez besoin

  • Un VPS (serveur privé virtuel) sous Ubuntu 24.04, avec Docker et le plugin Compose installés.
  • Un nom de domaine qui pointe vers ce serveur, si des machines externes accèdent à la gateway en TLS (transport layer security).
  • Au moins une clé d’API d’un provider.

La gateway n’effectue aucune inférence. Elle transmet les requêtes et renvoie les réponses en streaming. Sa charge CPU dépend donc du volume de requêtes, et non de la taille du modèle. Une machine avec 1 vCPU peut faire fonctionner sans problème quelques applications internes. En revanche, la base de données augmente, car la gateway écrit une ligne de dépense pour chaque requête.

Écrivez d’abord config.yaml

Le fichier de configuration détermine les modèles qu’un client peut demander. Quatre sections de premier niveau sont importantes : model_list, litellm_settings, router_settings et 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 est le nom envoyé par vos clients. litellm_params.model est le modèle réel, indiqué avec provider/model. Nommez vos modèles selon leur fonction plutôt que selon leur fournisseur. Une application qui demande bulk continuera de fonctionner si vous décidez le mois prochain que bulk doit correspondre à un autre modèle.

api_key: os.environ/ANTHROPIC_API_KEY indique à LiteLLM de lire cette variable au moment de l’exécution. La clé littérale n’apparaît jamais dans le fichier, ce qui est important, car config.yaml est le fichier que vous committez.

Deux entrées portent volontairement le nom strong. Lorsque plusieurs déploiements utilisent le même model_name, le routeur les traite comme interchangeables et essaie l’autre si le premier échoue. C’est ainsi que strong continue de fonctionner lorsqu’un fournisseur rencontre un incident temporaire.

num_retries: 2 réessaie le même déploiement en cas d’erreur réessayable. Un fallback n’est déclenché qu’une fois ces tentatives épuisées. allowed_fails: 3 avec cooldown_time: 30 retire un déploiement de la rotation pendant 30 secondes après 3 échecs. Un fournisseur qui renvoie des erreurs 500 n’est donc plus essayé à chaque requête.

fallbacks et context_window_fallbacks ont des déclencheurs différents, et le second est le plus utile, mais souvent ignoré.

  • fallbacks est déclenché lorsque l’appel principal échoue.
  • context_window_fallbacks est déclenché lorsque le fournisseur rejette la requête parce qu’elle dépasse la fenêtre de contexte de ce modèle. Un prompt trop volumineux est alors envoyé à un modèle qui peut le traiter, au lieu de renvoyer une erreur à l’appelant.

Il existe également content_policy_fallbacks, pour le cas où un fournisseur refuse une requête en raison de sa politique de contenu. Activez-le uniquement si vous disposez d’une destination adaptée pour ces appels.

Déployer LiteLLM sur un VPS avec Docker Compose

Créez un répertoire contenant trois fichiers : config.yaml, docker-compose.yml et .env. Le guide de démarrage rapide en amont utilise le tag latest. Épinglez plutôt un tag de release afin que docker compose up -d le mois prochain vous donne la même gateway qu’aujourd’hui et qu’un rollback tienne sur une seule ligne.

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 lit .env deux fois ici. Une première fois pour remplacer ${POSTGRES_PASSWORD} directement dans le fichier Compose, et une seconde fois via env_file pour transmettre toutes les variables au conteneur.

v1.95.0 était la release courante en août 2026. Consultez la page des releases du projet et utilisez la version courante au moment du déploiement. Chaque release publie une signature. Vous pouvez donc vérifier l’image avant de lui faire confiance :

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

La ligne de port est 127.0.0.1:4000:4000. Elle publie le port sur l’interface loopback uniquement. Écrivez plutôt 4000:4000 : votre gateway sera alors accessible depuis tout Internet, car Docker ajoute ses propres règles dans la chaîne FORWARD d’iptables et celles-ci sont évaluées avant les règles d’ufw. Par conséquent, ufw deny 4000 ne bloque pas cet accès. C’est la cause la plus fréquente d’exposition d’une gateway auto-hébergée : voir comment Docker publie directement un port de conteneur en contournant ufw. Le trafic externe arrive ensuite via le reverse proxy.

Éviter d’inclure les clés des fournisseurs dans l’image

Le fichier .env contient tous les secrets. Il est transmis comme variable d’environnement au moment de l’exécution. Il n’est donc jamais intégré à l’image et n’est jamais commité.

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-...

Générez les deux clés LiteLLM avec une vraie source d’aléa, puis restreignez les permissions du fichier :

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

LITELLM_MASTER_KEY est l’identifiant d’administration. Il authentifie l’API de gestion et sert de mot de passe pour l’interface d’administration à l’adresse /ui. Aucune application ne doit jamais le détenir.

LITELLM_SALT_KEY chiffre les identifiants des fournisseurs stockés dans la base de données. Définissez-le une seule fois et ne le modifiez plus. Si vous le changez ultérieurement, les identifiants déjà stockés ne pourront plus être déchiffrés. La gateway démarrera normalement, puis chaque appel à ces fournisseurs échouera lors de l’authentification.

STORE_MODEL_IN_DB=True permet d’ajouter et de modifier des modèles depuis l’interface d’administration, sans modifier config.yaml. C’est pratique, mais la source de vérité est alors répartie entre deux emplacements. Décidez lequel fait autorité et notez cette décision à côté de la configuration.

Le raisonnement qui consiste à exclure les clés du fichier de configuration s’applique aussi aux outils que vous fournissez à un agent. Exclure les secrets des fournisseurs des agents IA présente cette approche, et fichiers env et secrets dans Docker Compose en décrit la mise en œuvre.

Démarrez la gateway et surveillez le premier boot :

docker compose up -d
docker compose logs -f litellm

Vérifier que le service fonctionne réellement

Il existe deux sondes sans authentification et une sonde avec authentification. Elles échouent pour des raisons différentes.

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

/health/liveliness ne nécessite aucune authentification et répond "I'm alive!" lorsque le processus fonctionne. /health/readiness ne nécessite pas non plus d’authentification. Il renvoie un objet JSON contenant "status": "healthy" et un champ db, ou renvoie 503 lorsque la base de données est inaccessible. Configurez votre monitoring sur la readiness, car la liveliness reste au vert sur une gateway qui ne peut résoudre aucune virtual key.

La vérification avec authentification est celle qui communique avec les providers :

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

Elle répond avec les tableaux healthy_endpoints et unhealthy_endpoints. Un modèle présent dans unhealthy_endpoints avec une erreur d’authentification signifie que la clé du provider dans .env est incorrecte ou absente. C’est l’échec que vous devez rechercher maintenant. Comme background_health_checks: true est défini, le proxy exécute automatiquement ces sondes toutes les health_check_interval secondes et /health renvoie le dernier résultat. Le polling de cet endpoint n’envoie donc pas une requête de test à vos providers à chaque appel.

Clés virtuelles et budgets par clé

Chaque application dispose de sa propre clé, créée à partir de la clé maître.

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 réponse contient un champ key qui commence par sk-. Cette chaîne est transmise à l’application. C’est la seule donnée d’authentification qu’elle reçoit.

  • models est une allowlist des opérations que cette clé peut demander. La clé ci-dessus peut demander bulk, et rien d’autre.
  • max_budget: 5 avec budget_duration: "30d" définit un budget de cinq dollars américains par période glissante de 30 jours. La clé cesse ensuite de fonctionner.
  • rpm_limit et tpm_limit limitent respectivement le nombre de requêtes par minute et de tokens par minute pour cette seule clé.
  • key_alias est le libellé que vous retrouverez dans le journal des dépenses six semaines plus tard. Définissez-le toujours.

Lorsque le budget est épuisé, l’appel échoue avec HTTP 401 et un corps de réponse de cette forme :

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

Le code d’état est à l’origine de la confusion. Une bibliothèque cliente interprète 401 comme un problème d’authentification. Le développeur qui lit la stack trace commence donc par vérifier si la clé est valide. Journalisez le corps de la réponse avec le code d’état. Sinon, l’épuisement du budget ressemble systématiquement à des identifiants non valides.

Inspectez et modifiez les clés via la même API de gestion :

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 budget appliqué au niveau de la gateway reste effectif même lorsque le problème vient de l’agent lui-même. C’est pourquoi il constitue la base du contrôle des coûts des agents IA sur un VPS.

Envoyer les traitements en volume vers un modèle économique

Configurez le client pour utiliser la gateway. Indiquez l’URL de base, la clé et le nom du modèle :

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."}]
  }'

Toutes les bibliothèques clientes OpenAI fonctionnent de la même manière : définissez base_url sur https://gateway.example.com/v1 et api_key sur la clé virtuelle.

La politique de routage de config.yaml s’applique désormais sans que l’appelant le sache. Une requête pour bulk est envoyée vers le modèle économique. Si cet appel échoue après ses nouvelles tentatives, la requête est réessayée avec strong. Si le prompt est trop long pour bulk, context_window_fallbacks l’envoie vers strong au lieu de renvoyer une erreur. Les traitements en volume, comme une passe de classification ou la synthèse d’un backlog, utilisent par défaut le modèle économique. Seules les requêtes complexes coûtent davantage.

C’est également dans ce cas qu’une gateway est utile avec des agents qui utilisent des outils. Un serveur MCP (model context protocol) sur le même VPS et l’agent qui le pilote peuvent tous deux utiliser un seul endpoint. Le modèle utilisé peut ainsi changer sans redéployer aucun des deux.

Comment savoir qu’un fallback a eu lieu ?

C’est le mode de défaillance qui coûte de l’argent, car rien ne semble cassé. Un fallback réussi renvoie HTTP 200 avec un corps de réponse normal. Votre modèle économique peut être indisponible pendant une journée, chaque appel étant discrètement traité par le modèle coûteux, et la première preuve apparaît sur la facture.

Les preuves existent bien, dans les en-têtes de réponse. Demandez-les :

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 correspond à ce que le client a demandé. x-litellm-model-id correspond au déploiement qui a répondu. Lorsque ces deux valeurs diffèrent, un fallback a eu lieu.
  • x-litellm-attempted-fallbacks et x-litellm-attempted-retries les comptent. Pour un appel normal, les deux valeurs sont à 0.
  • x-litellm-response-cost correspond au coût de cet appel en dollars américains.
  • x-litellm-call-id est l’identifiant qui permet de retrouver le même appel dans vos journaux.

Enregistrez x-litellm-attempted-fallbacks pour chaque requête et déclenchez une alerte lorsqu’il cesse d’être égal à 0. Ce nombre suffit à distinguer une routing policy fonctionnelle d’une routing policy qui est devenue silencieusement « toujours utiliser le modèle coûteux ».

La version complète repose sur le tracing et mérite sa propre configuration : Langfuse auto-hébergé pour le tracing des appels d’agent. LiteLLM fournit le callback ; son intégration nécessite donc deux lignes et les credentials.

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

Définissez aussi failure_callback et success_callback. Si vous les omettez, vous ne conservez que les traces des requêtes pour lesquelles rien ne s’est mal passé. Indépendamment de cela, LiteLLM écrit une ligne de dépense par requête dans Postgres, et l’Admin UI à /ui lit cette table. Elle grossit avec le trafic ; surveillez donc son utilisation sur un petit disque.

Mettre la passerelle derrière un reverse proxy

Rien depuis l’extérieur ne doit atteindre le port 4000. Terminez TLS dans nginx ou Caddy, puis transférez les requêtes vers l’adresse 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;
}

Ce sont les deux lignes que l’on omet le plus souvent. proxy_buffering off est importante, car une réponse streaming est une suite de server-sent events. Lorsque le buffering est activé, nginx conserve les fragments jusqu’à la fin de la réponse. Le client reste donc silencieux, puis reçoit tout en une seule fois. proxy_read_timeout 600s est importante, car une génération longue dépasse le délai par défaut de 60 secondes de nginx. Le client reçoit alors une erreur 504, et le journal d’erreurs contient upstream timed out (110: Connection timed out) while reading response header from upstream.

Pour le certificat, Certbot avec Let’s Encrypt sur nginx est la solution la plus rapide. Si le serveur héberge déjà plusieurs conteneurs, Traefik devant plusieurs applications Compose gère le routage et les certificats au même endroit.

La passerelle est désormais un point de défaillance unique

Soyez réaliste sur ce que vous avez construit. Toutes vos applications dépendent désormais d’un seul conteneur sur un seul VPS. Tant qu’il est indisponible, aucune application ne peut appeler le moindre modèle, y compris les fournisseurs qui fonctionnent parfaitement. Quatre conséquences en découlent.

  • Une mauvaise configuration arrête tout en même temps. restart: unless-stopped redémarre un processus qui plante, y compris un conteneur incapable d’analyser config.yaml, en boucle. Consultez docker compose logs litellm après chaque modification de configuration et effectuez ces modifications lorsque vous pouvez les surveiller.
  • Postgres se trouve dans le chemin des requêtes. La recherche des clés virtuelles et l’enregistrement des dépenses utilisent tous deux cette base. /health/readiness renvoie 503 pour vous avertir que la passerelle fonctionne, mais ne peut effectuer aucune de ces opérations.
  • Pour monter en charge, ajoutez des instances au lieu d’augmenter la taille d’une seule instance. Les recommandations du projet prévoient un worker par instance (--num_workers 1) et plusieurs instances partageant une même base de données. Deux petites passerelles placées derrière un load balancer éliminent le conteneur unique du point de défaillance. Elles n’éliminent pas la base de données.
  • Sauvegardez ce que vous ne pouvez pas régénérer. Il s’agit de config.yaml et .env, ainsi que d’un pg_dump de la base de données. La perte de LITELLM_SALT_KEY rend inutilisables les identifiants chiffrés des fournisseurs contenus dans ce dump. Le fichier d’environnement et le dump doivent donc être inclus dans la même tâche de sauvegarde : sauvegardes restic sur un stockage externe.

Pour effectuer une mise à niveau, modifiez le tag de l’image, puis exécutez docker compose up -d. LiteLLM exécute prisma migrate deploy au démarrage par défaut. Le nouveau conteneur migre donc le schéma de la base de données lors de son premier démarrage. Effectuez le dump avant de modifier le tag, car restaurer l’ancienne image n’annule pas une migration déjà exécutée.

FAQ

LiteLLM ajoute-t-il une latence perceptible à chaque appel ?

Le projet annonce 8 ms au 95e percentile à 1000 requêtes par seconde, comme indiqué dans son README en août 2026. Considérez cette valeur comme une donnée du fournisseur. La distance réseau entre vos applications et la gateway est le facteur qui modifie réellement votre latence, car vous ajoutez un aller-retour à chaque appel. Exécutez la gateway dans la même région que les applications qui l’utilisent, puis mesurez votre propre surcharge avec l’en-tête x-litellm-overhead-duration-ms sur une réponse réelle.

Pourquoi le streaming a-t-il cessé de fonctionner après le placement de nginx devant la gateway ?

Parce que nginx met en tampon les réponses upstream par défaut, alors qu’une réponse en streaming est une série de server-sent events. Lorsque proxy_buffering est activé, nginx collecte les fragments et ne les transmet qu’à la fin de la réponse. Le client reste donc en attente sans recevoir de données, puis obtient toute la réponse d’un coup. Définissez proxy_buffering off; dans le bloc location. Augmentez aussi proxy_read_timeout dans ce même bloc, car une génération longue dépasse sinon la valeur par défaut de 60 secondes de nginx et le client reçoit une erreur 504.

Que se passe-t-il lorsqu’une clé virtuelle n’a plus de budget ?

L’appel échoue avec HTTP 401 et un corps de la forme ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07. Le code 401 est trompeur : une bibliothèque cliente le signale comme un échec d’authentification. Les utilisateurs vérifient alors si la clé est valide au lieu de lire le message. Journalisez le corps de la réponse avec le code d’état. Vérifiez la position réelle de la clé avec /key/info?key=sk-... et la clé master, puis augmentez la limite avec /key/update si le budget était trop bas.

La gateway peut-elle router vers un modèle local comme vers des modèles hébergés ?

Oui. Il s’agit d’une entrée supplémentaire dans model_list. Utilisez le préfixe ollama_chat/ avec un api_base, par exemple model: ollama_chat/llama3.1 avec api_base: http://ollama:11434. Depuis un conteneur, localhost désigne ce conteneur. Utilisez donc le nom du service Compose ou l’adresse de l’hôte sur le réseau Docker, jamais 127.0.0.1. Le déploiement du modèle local est une tâche distincte. Consultez auto-héberger un LLM avec Ollama sur un VPS.