SSD Nodes Learn 🎉 VPS vanaf $5.50/mnd
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-13

LiteLLM zelf hosten als LLM gateway

Host LiteLLM als centrale gateway voor al uw LLM providers. Beheer OpenAI-compatibele endpoints, stel budgetten per virtuele key in en configureer eenvoudig model-fallbacks.

Wat een self-hosted LLM gateway doet

LiteLLM is een open source LLM gateway die u zelf host: één HTTP-endpoint dat door al uw applicaties wordt aangeroepen en dat elk verzoek doorstuurt naar de provider die het moet afhandelen. LLM staat voor large language model. De gateway spreekt de OpenAI chat completions API (application programming interface), waardoor elke clientbibliotheek die al met OpenAI communiceert, ermee werkt na twee wijzigingen: de base URL en de key.

Die ene laag van indirectie is het doel. Uw applicaties hoeven geen inloggegevens van providers meer te bewaren. Het wisselen van een model wordt één regel in een configuratiebestand op de server in plaats van een codewijziging in vijf services. En omdat elk verzoek door één proces gaat, hebt u een centrale plek voor budgetbeheer en het bijhouden van de gemaakte kosten.

Dit is wat u hebt zodra het draait:

  • Eén endpoint. Applicaties richten zich op https://gateway.example.com/v1 en vragen om een modelnaam die u zelf hebt bedacht, zoals bulk of strong.
  • Virtuele keys. Elke applicatie krijgt een eigen key met een eigen toegestane lijst van modellen en een eigen bestedingslimiet. U kunt er één intrekken zonder de andere te beïnvloeden.
  • Fallbacks. Een mislukt verzoek of een te grote prompt wordt automatisch opnieuw geprobeerd met een ander model.
  • Een logboek. Elk verzoek schrijft een regel met de bijbehorende kosten, zodat u altijd kunt achterhalen welke applicatie het budget heeft verbruikt.

Waarom u de gateway zelf beheert

Een managed router heeft dezelfde vorm, maar bevat het proces van een derde partij dat tussen elk verzoek staat. Wanneer u deze zelf beheert, blijven uw provider-keys en uw prompt-tekst op een server die u zelf controleert. Hier hangt een reëel kostenplaatje aan: u beheert nu de component waar elke applicatie afhankelijk van is. Het laatste gedeelte van deze handleiding gaat over die kosten, aangezien dit het onderdeel is dat in de meeste documentatie wordt weggelaten.

Wat u nodig heeft

  • Een VPS (virtual private server) met Ubuntu 24.04, waarop Docker en de Compose-plugin zijn geïnstalleerd.
  • Een domeinnaam die naar de server verwijst, indien machines buiten het netwerk de gateway via TLS (transport layer security) moeten bereiken.
  • Minimaal één API-sleutel van een provider.

De gateway voert zelf geen inferentie uit. Deze stuurt verzoeken door en streamt antwoorden terug, waardoor de CPU-belasting afhangt van het aantal verzoeken in plaats van de modelgrootte. Een server met 1 vCPU kan zonder problemen een handvol interne applicaties aan. Wat wel groeit, is de database, omdat de gateway voor elk verzoek een rij met verbruiksgegevens schrijft.

Schrijf eerst config.yaml

Het configuratiebestand bepaalt welke modellen een client mag opvragen. Vier secties op het hoogste niveau zijn van belang: model_list, litellm_settings, router_settings en 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 is de naam die uw clients verzenden. litellm_params.model is het werkelijke model, geschreven als provider/model. Benoem uw modellen naar de taak in plaats van naar de leverancier. Een applicatie die vraagt om bulk blijft werken wanneer u volgende maand besluit dat bulk een ander model moet zijn.

api_key: os.environ/ANTHROPIC_API_KEY vertelt LiteLLM om die variabele tijdens runtime uit te lezen. De letterlijke sleutel verschijnt nooit in het bestand; dit is belangrijk omdat config.yaml het bestand is dat u commit.

Twee vermeldingen delen de naam strong, met opzet. Wanneer meer dan één deployment dezelfde model_name draagt, behandelt de router ze als uitwisselbaar en probeert hij de andere wanneer de eerste faalt. Dat is hoe strong overleeft wanneer een provider een slecht uur heeft.

num_retries: 2 voert opnieuw een poging uit bij dezelfde deployment bij een fout die herstelbaar is. Een fallback wordt pas geactiveerd nadat die pogingen zijn verbruikt. allowed_fails: 3 met cooldown_time: 30 haalt een deployment gedurende 30 seconden uit de rotatie zodra deze 3 keer is mislukt, zodat een provider die 500-fouten teruggeeft niet bij elk verzoek opnieuw wordt geprobeerd.

fallbacks en context_window_fallbacks hebben verschillende triggers, en de tweede is de nuttige die mensen vaak overslaan.

  • fallbacks wordt geactiveerd wanneer de primaire aanroep mislukt.
  • context_window_fallbacks wordt geactiveerd wanneer de provider het verzoek afwijst omdat het langer is dan het contextvenster van dat model, zodat een te grote prompt naar een model met voldoende ruimte gaat in plaats van een foutmelding naar de aanroeper terug te sturen.

Er is ook content_policy_fallbacks, voor een provider die weigert op basis van inhoudsbeleid. Stel dit alleen in als u een zinnige plek heeft om die aanroepen naartoe te sturen.

LiteLLM implementeren op een VPS met Docker Compose

Maak een map aan die drie bestanden bevat: config.yaml, docker-compose.yml en .env. De standaard quickstart haalt de latest tag op. Pin in plaats daarvan een specifieke release-tag, zodat docker compose up -d volgende maand dezelfde gateway oplevert als vandaag, en een rollback slechts één regel code vereist.

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 leest .env hier twee keer. Eén keer om ${POSTGRES_PASSWORD} binnen het compose-bestand zelf te vervangen, en één keer via env_file om elke variabele door te geven aan de container.

v1.95.0 was de huidige release in augustus 2026. Controleer de releases-pagina van het project en pin de versie die actueel is op het moment van implementatie. Elke release publiceert een handtekening, zodat u de image kunt verifiëren voordat u deze vertrouwt:

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

De poortregel is 127.0.0.1:4000:4000, wat de poort alleen op de loopback-interface publiceert. Schrijf in plaats daarvan 4000:4000 en uw gateway is bereikbaar vanaf het hele internet, omdat Docker zijn eigen regels toevoegt aan de iptables FORWARD chain en deze worden geëvalueerd vóór die van ufw, waardoor ufw deny 4000 het niet blokkeert. Dit is de meest voorkomende manier waarop een zelfgehoste gateway onbedoeld open komt te staan: zie hoe Docker een containerpoort publiceert en daarbij ufw omzeilt. Verkeer van buitenaf komt in plaats daarvan binnen via de reverse proxy.

Houd provider-sleutels buiten de image

Het .env-bestand bevat alle geheimen. Het wordt tijdens runtime als environment-variabele doorgegeven, waardoor het nooit in de image wordt ingebakken en nooit wordt gecommit.

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

Genereer de twee LiteLLM-sleutels met echte willekeur en beveilig het bestand vervolgens:

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

LITELLM_MASTER_KEY is het beheerderswachtwoord. Dit verifieert de management-API en fungeert als wachtwoord voor de Admin UI op /ui. Geen enkele applicatie mag dit ooit opslaan.

LITELLM_SALT_KEY versleutelt provider-referenties die in de database zijn opgeslagen. Stel dit eenmalig in en wijzig het daarna niet meer. Als u dit later wijzigt, kunnen de reeds opgeslagen referenties niet meer worden ontsleuteld. De gateway start dan normaal op, maar elke aanroep naar die providers zal falen bij de authenticatie.

STORE_MODEL_IN_DB=True stelt u in staat om modellen toe te voegen en te bewerken via de Admin UI zonder config.yaml aan te passen. Dat is handig, maar het verdeelt uw bron van waarheid in tweeën. Bepaal welke bron leidend is en noteer deze beslissing naast de configuratie.

De redenatie om sleutels uit het configuratiebestand te houden, is dezelfde als voor het weghouden van sleutels uit de tools die u aan een agent geeft. Provider-geheimen buiten AI-agents houden behandelt dat patroon, en env-bestanden en geheimen in Docker Compose behandelt de werking.

Start de service en controleer de eerste opstartfase:

docker compose up -d
docker compose logs -f litellm

Controleer of het daadwerkelijk werkt

Er zijn twee probes zonder authenticatie en één met authenticatie; deze falen om verschillende redenen.

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

/health/liveliness vereist geen authenticatie en antwoordt met "I'm alive!" zolang het proces actief is. /health/readiness vereist eveneens geen authenticatie. Het retourneert een JSON-object met "status": "healthy" en een db-veld, of een 503-foutmelding wanneer de database onbereikbaar is. Richt uw monitoring op readiness, aangezien liveliness groen blijft op een gateway die geen enkele virtuele sleutel kan opzoeken.

De geauthenticeerde controle is de check die communiceert met providers:

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

Deze antwoordt met healthy_endpoints en unhealthy_endpoints-arrays. Een model dat in unhealthy_endpoints staat met een authenticatiefout betekent dat de providersleutel in .env onjuist of afwezig is; dit is de fout die u nu wilt opsporen. Omdat background_health_checks: true is ingesteld, voert de proxy deze probes zelfstandig elke health_check_interval seconden uit en retourneert /health het laatste resultaat. Het pollen hiervan stuurt dus niet telkens een testverzoek naar uw providers.

Virtuele sleutels en budgetten per sleutel

Elke applicatie krijgt een eigen sleutel, aangemaakt op basis van de hoofdsleutel.

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
  }'

Het antwoord bevat een key-veld dat begint met sk-. Die tekenreeks is wat de applicatie ontvangt, en het is het enige dat de applicatie ooit krijgt.

  • models is een toegestane lijst van wat deze sleutel mag opvragen. De bovenstaande sleutel kan alleen om bulk vragen en niets anders.
  • max_budget: 5 met budget_duration: "30d" staat voor vijf Amerikaanse dollar per 30 dagen, waarna de sleutel stopt met werken.
  • rpm_limit en tpm_limit begrenzen het aantal verzoeken per minuut en tokens per minuut voor uitsluitend deze sleutel.
  • key_alias is wat u zes weken later zult herkennen in het uitgavenoverzicht. Stel dit altijd in.

Wanneer het budget op is, mislukt de aanroep met HTTP 401 en een body met deze structuur:

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

De statuscode maakt dit verwarrend. Een clientbibliotheek rapporteert 401 als een authenticatieprobleem, waardoor de ontwikkelaar die de stacktrace leest, begint te controleren of de sleutel geldig is. Log de response body naast de statuscode, anders lijkt budgetuitputting elke keer op een defecte inloggegevens.

Inspecteer en pas sleutels aan via dezelfde management-API:

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}'

Een budget dat bij de gateway wordt afgedwongen, blijft van kracht, zelfs wanneer het probleem bij de agent zelf ligt. Daarom vormt dit de basis van kostenbeheersing voor AI-agents op een VPS.

Bulk-taken naar een goedkoop model sturen

Wijs een client naar de gateway. Basis-URL, sleutel, modelnaam:

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

Elke OpenAI-clientbibliotheek werkt op dezelfde manier: stel base_url in op https://gateway.example.com/v1 en api_key op de virtuele sleutel.

Het routeringsbeleid uit config.yaml is nu van toepassing zonder dat de aanroeper dit merkt. Een verzoek voor bulk gaat naar het goedkope model. Als die aanroep na de retries mislukt, wordt het verzoek opnieuw geprobeerd tegen strong. Als de prompt te lang is voor bulk, stuurt context_window_fallbacks deze naar strong in plaats van een foutmelding te retourneren. Bulk-taken zoals een classificatieronde of het samenvatten van een backlog worden standaard goedkoop uitgevoerd, en alleen de complexe verzoeken kosten meer.

Dit is ook waar een gateway zijn waarde bewijst bij agents die tools gebruiken. Een MCP (model context protocol) server op dezelfde VPS en de agent die deze aanstuurt, kunnen beide naar hetzelfde eindpunt wijzen, waardoor het model erachter kan worden gewijzigd zonder dat een van beide opnieuw hoeft te worden uitgerold.

Hoe stelt u vast dat er een fallback heeft plaatsgevonden?

Dit is het type fout dat geld kost, omdat er aan de buitenkant niets defect lijkt. Een geslaagde fallback geeft een HTTP 200-statuscode terug met een normale response body. Uw goedkope model kan een dag lang offline zijn, waarbij elk verzoek stilletjes door het dure model wordt afgehandeld, en het eerste bewijs is de factuur.

Het bewijs is echter wel aanwezig in de response headers. Vraag deze op:

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 is waar de client om vroeg. x-litellm-model-id is de deployment die antwoordde. Wanneer deze twee niet overeenkomen, heeft er een fallback plaatsgevonden.
  • x-litellm-attempted-fallbacks en x-litellm-attempted-retries tellen deze gebeurtenissen. Bij een geslaagd verzoek zijn beide 0.
  • x-litellm-response-cost zijn de kosten van dat specifieke verzoek in Amerikaanse dollars.
  • x-litellm-call-id is de identifier die u gebruikt om hetzelfde verzoek in uw logs terug te vinden.

Registreer x-litellm-attempted-fallbacks bij elk verzoek en stel een alert in wanneer deze waarde niet langer 0 is. Dat ene getal maakt het verschil tussen een routeringsbeleid dat werkt en een routeringsbeleid dat stilletjes is veranderd in "gebruik altijd het dure model".

De volledige versie hiervan is tracing, en dat verdient een eigen configuratie: self-hosted Langfuse voor het tracen van agent-aanroepen. LiteLLM levert de callback mee, dus het koppelen ervan kost slechts twee regels code plus de inloggegevens.

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

Stel zowel failure_callback als success_callback in. Slaat u dit over, dan bewaart u alleen traces van verzoeken waarbij niets misging. Los van dit alles schrijft LiteLLM per verzoek een regel met de kosten naar Postgres, en de Admin UI op /ui leest die tabel uit. Deze tabel groeit mee met het verkeer, dus houd de schijfruimte in de gaten.

Plaats de gateway achter een reverse proxy

Niets buiten de server mag poort 4000 bereiken. Beëindig TLS in Nginx of Caddy en stuur het verkeer door naar het loopback-adres.

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;
}

Twee van deze regels worden vaak vergeten. proxy_buffering off is van belang omdat een streaming completion een reeks server-sent events is; als buffering is ingeschakeld, houdt Nginx de brokken vast totdat het antwoord volledig is, waardoor de client niets ontvangt en vervolgens alles tegelijk krijgt. proxy_read_timeout 600s is van belang omdat een lange generatie de standaardlimiet van 60 seconden van Nginx overschrijdt; wanneer dit gebeurt, krijgt de client een 504-foutmelding terwijl het foutenlogboek upstream timed out (110: Connection timed out) while reading response header from upstream registreert.

Voor het certificaat is Certbot met Let's Encrypt op Nginx de kortste route. Als de server al meerdere containers bedient, regelt Traefik voor meerdere Compose-applicaties de routering en certificaten op één centrale plek.

De gateway is nu een single point of failure

Wees eerlijk over wat u heeft gebouwd. Elke applicatie die u beheert, is nu afhankelijk van één container op één VPS. Zolang deze offline is, kan niets een model aanroepen, zelfs niet bij providers die volledig operationeel zijn. Hieruit volgen vier zaken.

  • Een foutieve configuratie legt alles tegelijk plat. restart: unless-stopped herstart bij een crash, en het herstart een container die config.yaml niet kan inlezen, keer op keer. Lees docker compose logs litellm na elke configuratiewijziging en voer wijzigingen door wanneer u tijd heeft om deze te controleren.
  • Postgres bevindt zich in het verzoekpad. Zowel het opzoeken van virtuele sleutels als het registreren van verbruik maken hier gebruik van. Een /health/readiness die een 503-foutmelding geeft, is uw waarschuwing dat de gateway wel draait, maar geen van beide taken kan uitvoeren.
  • Schaal door instanties toe te voegen, niet door één instantie groter te maken. De richtlijn van het project zelf is één worker per instantie (--num_workers 1), waarbij meerdere instanties één database delen. Twee kleine gateways achter een load balancer elimineren de enkele container als risicofactor. Ze elimineren echter niet de database.
  • Maak back-ups van wat u niet opnieuw kunt genereren. Dat betreft config.yaml en .env, samen met een pg_dump van de database. Het verliezen van LITELLM_SALT_KEY maakt de versleutelde provider-inloggegevens in die dump onbruikbaar, dus het env-bestand en de dump horen in dezelfde back-uptaak thuis: restic back-ups naar externe opslag.

Upgraden betekent de image-tag aanpassen en docker compose up -d uitvoeren. LiteLLM voert standaard prisma migrate deploy uit bij het opstarten, dus de nieuwe container migreert het databaseschema bij de eerste keer opstarten. Maak de dump voordat u de tag wijzigt, want het terugplaatsen van de oude image maakt een reeds uitgevoerde migratie niet ongedaan.

FAQ

Voegt LiteLLM merkbare latentie toe aan elk verzoek?

Het project rapporteert 8 ms op het 95e percentiel bij 1000 verzoeken per seconde, zoals vermeld in de README in augustus 2026. Beschouw dit als een cijfer van de leverancier. De factor die uw latentie daadwerkelijk beïnvloedt, is de netwerkafstand tussen uw applicaties en de gateway, aangezien u één extra round-trip toevoegt aan elk verzoek. Draai de gateway in dezelfde regio als de applicaties die deze aanroepen en meet vervolgens uw eigen overhead met de x-litellm-overhead-duration-ms-header op een daadwerkelijk antwoord.

Waarom werkt streaming niet meer nadat ik nginx ervoor heb geplaatst?

Omdat nginx standaard upstream-antwoorden buffert en een streaming-completion een reeks server-sent events is. Met proxy_buffering ingeschakeld verzamelt nginx de brokken en geeft deze pas vrij wanneer het antwoord volledig is; de client wacht dus in stilte en ontvangt daarna het hele antwoord in één keer. Stel proxy_buffering off; in het location-blok in. Verhoog proxy_read_timeout in hetzelfde blok, omdat een lange generatie anders de standaardlimiet van 60 seconden van nginx overschrijdt en de client een 504-foutmelding krijgt.

Wat gebeurt er als een virtual key geen budget meer heeft?

Het verzoek faalt met HTTP 401 en een body in de vorm van ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07. De 401 is de valstrik: een clientbibliotheek rapporteert dit als een authenticatiefout, waardoor gebruikers de geldigheid van de sleutel controleren in plaats van het bericht te lezen. Log de response body samen met de statuscode. Controleer de werkelijke status van de sleutel met /key/info?key=sk-... ten opzichte van de master key en verhoog het plafond met /key/update als het budget te laag was ingesteld.

Kan de gateway zowel naar een lokaal model als naar gehoste modellen routeren?

Ja, dit is simpelweg een extra vermelding in model_list. Gebruik het ollama_chat/-voorvoegsel met een api_base, bijvoorbeeld model: ollama_chat/llama3.1 naast api_base: http://ollama:11434. Vanuit een container betekent localhost die specifieke container; gebruik daarom de Compose-servicenaam of het adres van de host op het Docker-netwerk, nooit 127.0.0.1. Het opzetten van het lokale model is een aparte taak: zie een LLM zelf hosten met Ollama op een VPS.