Cara Self-Host LiteLLM sebagai Gateway LLM
Jalankan satu endpoint yang kompatibel dengan OpenAI untuk semua provider melalui LiteLLM di VPS, lengkap dengan virtual key, anggaran per key, fallback, dan image terpin.
Fungsi gateway LLM yang di-host sendiri
LiteLLM adalah gateway LLM sumber terbuka yang Anda host sendiri: satu endpoint HTTP yang digunakan semua aplikasi Anda, lalu meneruskan setiap permintaan ke provider yang seharusnya menanganinya. LLM berarti large language model. Gateway ini menggunakan OpenAI chat completions API (application programming interface), sehingga library klien yang sudah berkomunikasi dengan OpenAI dapat digunakan melalui gateway ini setelah dua perubahan: base URL dan key.
Lapisan perantara ini adalah inti pendekatannya. Aplikasi Anda tidak lagi menyimpan kredensial provider. Penggantian model cukup dilakukan pada satu baris dalam file konfigurasi di server, bukan dengan mengubah kode pada lima service. Karena setiap panggilan melewati satu proses, Anda memiliki tempat untuk menetapkan anggaran dan mencatat pengeluaran.
Setelah gateway berjalan, Anda memiliki hal-hal berikut:
- Satu endpoint. Aplikasi mengarah ke
https://gateway.example.com/v1dan meminta nama model yang Anda tentukan sendiri, sepertibulkataustrong. - Virtual key. Setiap aplikasi memperoleh key sendiri, dengan allowlist model dan batas pengeluaran masing-masing. Anda dapat mencabut satu key tanpa memengaruhi key lainnya.
- Fallback. Panggilan yang gagal atau prompt yang terlalu besar akan otomatis dicoba ulang menggunakan model lain.
- Catatan dalam log. Setiap permintaan menulis satu baris yang memuat biayanya, sehingga pertanyaan "aplikasi mana yang menghabiskan biaya itu" memiliki jawabannya.
Mengapa gateway dijalankan sendiri
Router terkelola memiliki arsitektur yang sama, tetapi proses milik pihak lain berada di antara setiap permintaan. Dengan menjalankannya sendiri, kunci provider dan teks prompt tetap berada pada server yang Anda kendalikan. Biayanya nyata: kini Anda mengoperasikan komponen yang menjadi dependensi setiap aplikasi. Bagian terakhir panduan ini membahas biaya tersebut karena aspek ini sering tidak dibahas dalam panduan lain.
Yang Anda perlukan
- VPS (virtual private server) yang menjalankan Ubuntu 24.04, dengan Docker dan plugin Compose terpasang.
- Nama domain yang mengarah ke VPS tersebut jika mesin di luar server akan mengakses gateway melalui TLS (transport layer security).
- Setidaknya satu kunci API provider.
Gateway tidak menjalankan inference. Gateway meneruskan request dan mengalirkan jawaban kembali, sehingga beban CPU mengikuti volume request, bukan ukuran model. Server dengan 1 vCPU dapat menjalankan beberapa aplikasi internal tanpa masalah. Database yang bertambah besar karena gateway menulis satu baris pengeluaran untuk setiap request.
Tulis config.yaml terlebih dahulu
File konfigurasi menentukan model yang dapat diminta oleh client. Ada empat section tingkat teratas yang penting: model_list, litellm_settings, router_settings, dan 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: 300model_name adalah nama yang dikirim oleh client Anda. litellm_params.model adalah model sebenarnya, yang ditulis sebagai provider/model. Beri nama model berdasarkan fungsinya, bukan vendornya. Aplikasi yang meminta bulk akan tetap berfungsi ketika bulan depan Anda memutuskan bahwa bulk harus menggunakan model yang berbeda.
api_key: os.environ/ANTHROPIC_API_KEY memberi tahu LiteLLM untuk membaca variabel tersebut saat runtime. Key literal tidak pernah muncul di dalam file. Hal ini penting karena config.yaml adalah file yang Anda commit.
Dua entry menggunakan nama strong, dan ini memang disengaja. Jika lebih dari satu deployment memiliki model_name yang sama, router memperlakukannya sebagai deployment yang dapat saling menggantikan dan mencoba deployment lain ketika deployment pertama gagal. Dengan demikian, strong tetap berfungsi ketika salah satu provider mengalami gangguan selama beberapa waktu.
num_retries: 2 mencoba ulang deployment yang sama setelah terjadi error yang dapat dicoba ulang. Fallback baru dijalankan setelah seluruh percobaan ulang tersebut habis. allowed_fails: 3 dengan cooldown_time: 30 mengeluarkan deployment dari rotasi selama 30 detik setelah deployment tersebut gagal 3 kali. Dengan begitu, provider yang mengembalikan status 500 tidak terus dicoba pada setiap request.
fallbacks dan context_window_fallbacks memiliki pemicu yang berbeda, dan pemicu kedua adalah yang berguna tetapi sering dilewati.
fallbacksdijalankan ketika pemanggilan utama gagal.context_window_fallbacksdijalankan ketika provider menolak request karena panjangnya melebihi context window model tersebut. Dengan demikian, prompt yang terlalu besar diarahkan ke model yang memiliki ruang cukup, bukan mengembalikan error kepada pemanggil.
Ada juga content_policy_fallbacks, yang digunakan ketika provider menolak request berdasarkan kebijakan konten. Tetapkan ini hanya jika Anda memiliki tujuan yang sesuai untuk meneruskan request tersebut.
Deploy LiteLLM pada VPS dengan Docker Compose
Buat direktori yang berisi tiga file: config.yaml, docker-compose.yml, dan .env. Quickstart upstream menggunakan tag latest. Tetapkan tag rilis secara eksplisit agar docker compose up -d bulan depan menghasilkan gateway yang sama seperti hari ini, dan rollback dapat dilakukan dengan mengubah satu baris.
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 membaca .env dua kali di sini. Pertama, untuk mengganti ${POSTGRES_PASSWORD} di dalam file Compose itu sendiri. Kedua, melalui env_file untuk meneruskan setiap variabel ke dalam container.
v1.95.0 adalah rilis terbaru pada Agustus 2026. Periksa halaman rilis proyek dan tetapkan versi yang sedang terbaru saat Anda melakukan deployment. Setiap rilis menyediakan signature, sehingga Anda dapat memeriksa image sebelum mempercayainya:
cosign verify --key https://raw.githubusercontent.com/BerriAI/litellm/v1.95.0/cosign.pub ghcr.io/berriai/litellm:v1.95.0Baris port adalah 127.0.0.1:4000:4000, yang hanya memublikasikan port pada interface loopback. Jika Anda menulis 4000:4000, gateway dapat dijangkau dari seluruh Internet karena Docker menambahkan aturannya sendiri ke chain FORWARD iptables dan aturan tersebut dievaluasi sebelum aturan ufw. Karena itu, ufw deny 4000 tidak dapat menghentikannya. Ini adalah cara paling umum gateway self-hosted terbuka ke Internet: lihat cara Docker memublikasikan port container langsung melewati ufw. Trafik dari luar masuk melalui reverse proxy.
Simpan kunci provider di luar image
File .env menyimpan semua secret. File ini diberikan sebagai environment saat runtime, sehingga tidak pernah ditanamkan ke dalam image dan tidak pernah di-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-...Buat kedua kunci LiteLLM menggunakan sumber keacakan yang kuat, lalu batasi akses ke file tersebut:
printf 'sk-%s\n' "$(openssl rand -hex 32)"
chmod 600 .envLITELLM_MASTER_KEY adalah kredensial admin. Kredensial ini mengautentikasi management API dan menjadi password untuk Admin UI di /ui. Jangan pernah menyimpan kredensial ini pada aplikasi.
LITELLM_SALT_KEY mengenkripsi kredensial provider yang disimpan dalam database. Tetapkan nilainya sekali dan jangan mengubahnya. Jika nilainya diubah kemudian, kredensial yang sudah tersimpan tidak dapat didekripsi. Akibatnya, gateway berhasil start secara normal, tetapi setiap panggilan ke provider tersebut gagal saat autentikasi.
STORE_MODEL_IN_DB=True memungkinkan Anda menambahkan dan mengedit model dari Admin UI tanpa mengubah config.yaml. Cara ini praktis, tetapi membagi sumber kebenaran menjadi dua. Tentukan sumber yang menjadi acuan dan tuliskan keputusan tersebut di sebelah config.
Alasan untuk menyimpan kunci di luar file config sama dengan alasan untuk menyimpannya di luar tools yang Anda berikan kepada agent. Menyimpan secret provider di luar AI agent membahas pola tersebut, sedangkan file env dan secret dalam Docker Compose membahas penerapannya.
Jalankan layanan dan pantau boot pertama:
docker compose up -d
docker compose logs -f litellmPeriksa bahwa layanan benar-benar berjalan
Terdapat dua probe tanpa autentikasi dan satu probe dengan autentikasi. Ketiganya dapat gagal karena alasan yang berbeda.
curl -s http://127.0.0.1:4000/health/liveliness
curl -s http://127.0.0.1:4000/health/readiness/health/liveliness tidak memerlukan autentikasi dan memberikan respons "I'm alive!" selama proses masih berjalan. /health/readiness juga tidak memerlukan autentikasi. Endpoint ini mengembalikan objek JSON dengan field "status": "healthy" dan field db, atau mengembalikan 503 jika database tidak dapat dijangkau. Arahkan pemantauan ke readiness, karena liveliness tetap berstatus hijau pada gateway yang tidak dapat mencari satu pun kunci virtual.
Pemeriksaan dengan autentikasi adalah pemeriksaan yang berkomunikasi dengan provider:
curl -s http://127.0.0.1:4000/health \
-H "Authorization: Bearer $LITELLM_MASTER_KEY"Pemeriksaan ini memberikan respons berupa array healthy_endpoints dan unhealthy_endpoints. Model yang berada dalam unhealthy_endpoints dan mengalami error autentikasi menunjukkan bahwa provider key dalam .env salah atau belum diatur. Inilah kegagalan yang perlu ditemukan sekarang. Karena background_health_checks: true diatur, proxy menjalankan probe ini sendiri setiap health_check_interval detik dan /health mengembalikan hasil terakhir. Dengan demikian, polling endpoint tersebut tidak mengirimkan test request ke provider setiap kali.
Kunci virtual dan anggaran per kunci
Setiap aplikasi mendapatkan kuncinya sendiri, yang dibuat menggunakan master key.
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
}'Respons memuat field key yang diawali dengan sk-. String tersebut adalah yang diterima aplikasi, dan hanya itu yang pernah diterima aplikasi.
modelsadalah allowlist untuk permintaan yang boleh diajukan oleh kunci ini. Kunci di atas hanya dapat memintabulkdan tidak ada yang lain.max_budget: 5denganbudget_duration: "30d"adalah lima dolar AS selama 30 hari berjalan. Setelah itu, kunci berhenti berfungsi.rpm_limitdantpm_limitmembatasi jumlah request per menit dan token per menit hanya untuk kunci ini.key_aliasadalah nilai yang akan Anda kenali dalam spend log enam minggu kemudian. Selalu tetapkan nilai ini.
Setelah anggaran habis, pemanggilan gagal dengan HTTP 401 dan body dengan bentuk berikut:
ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07Kode status inilah yang membuat masalah ini membingungkan. Client library melaporkan 401 sebagai masalah autentikasi, sehingga developer yang membaca stack trace mulai memeriksa apakah kunci tersebut valid. Catat body respons bersama kode statusnya. Jika tidak, anggaran yang habis akan selalu tampak seperti kredensial yang rusak.
Periksa dan sesuaikan kunci melalui management API yang sama:
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}'Anggaran yang diberlakukan di gateway tetap berlaku meskipun masalahnya berasal dari agent itu sendiri. Karena itu, anggaran ini menjadi dasar pengendalian biaya untuk agent AI pada VPS.
Kirim pekerjaan massal ke model murah
Arahkan client ke gateway. Base URL, key, dan nama model:
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."}]
}'Semua library client OpenAI berperilaku sama: atur base_url ke https://gateway.example.com/v1 dan api_key ke virtual key.
Kebijakan routing dari config.yaml kini berlaku tanpa diketahui caller. Request untuk bulk diarahkan ke model murah. Jika pemanggilan tersebut gagal setelah seluruh percobaan ulang, request dicoba lagi pada strong. Jika prompt terlalu panjang untuk bulk, context_window_fallbacks mengarahkannya ke strong, bukan mengembalikan error. Pekerjaan massal seperti proses klasifikasi atau backlog yang perlu diringkas berjalan pada model murah secara default. Hanya request yang sulit yang dikenai biaya lebih tinggi.
Di sinilah gateway juga bermanfaat untuk agent yang menggunakan tool. Server MCP (model context protocol) pada VPS yang sama dan agent yang mengendalikannya dapat mengarah ke satu endpoint. Dengan demikian, model di belakangnya dapat diganti tanpa melakukan redeploy pada keduanya.
Bagaimana cara mengetahui bahwa fallback terjadi?
Ini adalah mode kegagalan yang menimbulkan biaya karena tidak ada yang tampak rusak. Fallback yang berhasil mengembalikan HTTP 200 dengan isi respons biasa. Model murah Anda dapat tidak berfungsi selama satu hari, setiap panggilan diam-diam dilayani oleh model mahal, dan bukti pertama muncul pada tagihan.
Buktinya ada di header respons. Minta header tersebut:
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-groupmenunjukkan deployment yang diminta client.x-litellm-model-idmenunjukkan deployment yang memberikan respons. Jika keduanya berbeda, fallback telah terjadi.x-litellm-attempted-fallbacksdanx-litellm-attempted-retriesmenghitungnya. Pada panggilan yang normal, keduanya bernilai 0.x-litellm-response-costmenunjukkan biaya satu panggilan tersebut dalam dolar AS.x-litellm-call-idadalah identifier yang digunakan untuk menemukan panggilan yang sama di log.
Catat x-litellm-attempted-fallbacks pada setiap request dan buat alert saat nilainya tidak lagi 0. Angka tersebut membedakan routing policy yang berfungsi dari routing policy yang diam-diam berubah menjadi "selalu gunakan model mahal".
Versi lengkapnya adalah tracing, dan fitur ini memerlukan setup tersendiri: Langfuse yang di-host sendiri untuk tracing panggilan agent. LiteLLM menyediakan callback tersebut, jadi konfigurasinya hanya memerlukan dua baris dan kredensial.
litellm_settings:
success_callback: ["langfuse"]
failure_callback: ["langfuse"]LANGFUSE_PUBLIC_KEY=pk-lf-...
LANGFUSE_SECRET_KEY=sk-lf-...
LANGFUSE_HOST=https://langfuse.example.comTetapkan failure_callback dan success_callback. Jika dilewati, satu-satunya trace yang tersimpan adalah trace ketika tidak terjadi kesalahan. Terlepas dari semua ini, LiteLLM menulis satu baris pengeluaran untuk setiap request ke Postgres, dan Admin UI di /ui membaca tabel tersebut. Tabel ini bertambah seiring traffic, jadi pantau penggunaannya pada disk berkapasitas kecil.
Tempatkan gateway di belakang reverse proxy
Tidak ada koneksi dari luar yang boleh mencapai port 4000. Lakukan TLS termination di nginx atau Caddy, lalu teruskan koneksi ke alamat 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;
}Dua baris tersebut sering diabaikan. proxy_buffering off penting karena streaming completion terdiri atas serangkaian server-sent events. Jika buffering di nginx aktif, nginx menahan potongan respons hingga respons selesai. Akibatnya, klien tidak menerima apa pun selama proses berlangsung, lalu menerima seluruh respons sekaligus. proxy_read_timeout 600s penting karena proses generasi yang lama dapat melebihi batas waktu default nginx, yaitu 60 detik. Jika itu terjadi, klien menerima error 504, sedangkan error log mencatat upstream timed out (110: Connection timed out) while reading response header from upstream.
Untuk sertifikat, Certbot dengan Let’s Encrypt pada nginx adalah cara singkat. Jika server sudah menjalankan beberapa container, Traefik di depan beberapa aplikasi Compose menangani routing dan sertifikat dari satu tempat.
Gateway sekarang menjadi satu titik kegagalan
Jelaskan dengan jujur apa yang telah Anda bangun. Semua aplikasi yang Anda kelola kini bergantung pada satu container di satu VPS. Saat container tersebut tidak aktif, tidak ada aplikasi yang dapat memanggil model apa pun, termasuk provider yang sebenarnya sehat. Ada empat hal yang perlu diperhatikan.
- Konfigurasi yang buruk dapat menghentikan semuanya sekaligus.
restart: unless-stoppedme-restart container yang mengalami crash, termasuk container yang tidak dapat mem-parsing config.yaml, berulang kali. Bacadocker compose logs litellmsetiap kali selesai mengubah konfigurasi, dan lakukan perubahan saat Anda memiliki waktu untuk memantaunya. - Postgres berada dalam jalur request. Pencarian virtual key dan pencatatan penggunaan sama-sama menggunakannya.
/health/readinessyang mengembalikan 503 adalah peringatan bahwa gateway berjalan, tetapi tidak dapat melakukan kedua fungsi tersebut. - Lakukan scale-out dengan menambahkan instance, bukan dengan memperbesar satu instance. Panduan proyek menyarankan satu worker per instance (
--num_workers 1), dengan beberapa instance berbagi satu database. Dua gateway kecil di belakang load balancer menghilangkan ketergantungan pada satu container. Namun, database tetap menjadi ketergantungan. - Cadangkan data yang tidak dapat dibuat ulang. Data tersebut adalah
config.yamldan.env, bersama denganpg_dumpdatabase. KehilanganLITELLM_SALT_KEYmembuat kredensial provider terenkripsi di dalam dump tersebut tidak berguna. Karena itu, env file dan dump harus berada dalam job backup yang sama: backup restic ke penyimpanan di luar server.
Upgrade dilakukan dengan mengedit image tag lalu menjalankan docker compose up -d. Secara default, LiteLLM menjalankan prisma migrate deploy saat startup. Dengan demikian, container baru memigrasikan skema database pada boot pertamanya. Buat dump sebelum mengubah tag, karena mengembalikan image lama tidak membatalkan migrasi yang sudah dijalankan.
FAQ
Apakah LiteLLM menambahkan latensi yang terasa pada setiap panggilan?
Proyek ini mencantumkan 8 ms pada persentil ke-95 dengan 1000 permintaan per detik, sebagaimana dinyatakan dalam README pada August 2026. Anggap angka tersebut sebagai angka dari vendor. Faktor yang benar-benar memengaruhi latensi Anda adalah jarak jaringan antara aplikasi dan gateway, karena setiap panggilan kini memerlukan satu perjalanan pulang-pergi tambahan. Jalankan gateway di region yang sama dengan aplikasi yang memanggilnya, lalu ukur overhead Anda sendiri dengan header x-litellm-overhead-duration-ms pada respons nyata.
Mengapa streaming berhenti berfungsi setelah saya menempatkan nginx di depannya?
Karena nginx secara default melakukan buffering pada respons upstream, sedangkan completion streaming terdiri dari serangkaian server-sent event. Saat proxy_buffering aktif, nginx mengumpulkan potongan respons dan baru mengirimkannya setelah respons selesai. Akibatnya, client tidak menerima data apa pun lalu menerima seluruh jawaban sekaligus. Atur proxy_buffering off; di dalam blok location. Naikkan proxy_read_timeout di blok yang sama, karena proses generasi yang lama dapat melampaui nilai default nginx, yaitu 60 detik, sehingga client menerima 504.
Apa yang terjadi ketika virtual key kehabisan anggaran?
Panggilan gagal dengan HTTP 401 dan body berbentuk ExceededBudget: Current spend for token: 7.2e-05; Max Budget for Token: 2e-07. Kode 401 dapat menyesatkan: library client melaporkannya sebagai kegagalan autentikasi, sehingga orang mulai memeriksa apakah key valid, bukan membaca pesannya. Catat body respons bersama kode statusnya. Konfirmasikan posisi sebenarnya key dengan /key/info?key=sk-... menggunakan master key, lalu naikkan batasnya dengan /key/update jika anggarannya terlalu rendah.
Dapatkah gateway merutekan ke model lokal selain model yang di-host?
Ya. Model tersebut menjadi satu entri tambahan dalam model_list. Gunakan prefix ollama_chat/ dengan api_base, misalnya model: ollama_chat/llama3.1 bersama api_base: http://ollama:11434. Dari dalam container, localhost berarti container tersebut, jadi gunakan nama service Compose atau alamat host pada jaringan Docker, bukan 127.0.0.1. Menjalankan model lokal adalah tugas terpisah. Lihat self-hosting LLM dengan Ollama pada VPS.