Deployment guide

Self-Host LiteLLM on a VPS: Your Private LLM API Gateway

Deploy on a VPS Cloud →

Tutorial

Self-hosting

Self-Host LiteLLM on a VPS: Your Private LLM API Gateway

Your application calls one endpoint. LiteLLM decides which model handles the request — your local Ollama, Anthropic Claude, GPT-4, or any of 100+ other providers. It speaks OpenAI's API so no SDK change is ever needed. Self-hosted on a VPS, it adds rate limiting, budget caps per API key, and a built-in analytics dashboard — so you can give multiple teams access to AI without worrying about runaway costs.

9 min read12 steps
Contents· Why LiteLLM is the missing layer for self-hosted AI stacks1/12
  1. 01Why LiteLLM is the missing layer for self-hosted AI stacks
  2. 02What LiteLLM gives you out of the box
  3. 03Requirements
  4. 04Deploy LiteLLM on a VPS
  5. 05Supply chain: pin, verify, rotate keys
  6. 06Know what is really running on your host
  7. 07Pin by digest rather than by tag
  8. 08Update without surprises
  9. 09If 1.107.0 ever ran on your host: rotate every key
  10. 10What to renew
  11. 11Watch outbound traffic
  12. 12The official documentation

Why LiteLLM is the missing layer for self-hosted AI stacks

Ollama is great for running local models. Open WebUI wraps it in a chat interface. But the moment you start building applications — not just chatting — you need something in between: a proxy that normalises the API surface, tracks costs per project, and lets you swap models without touching app code. That is exactly what LiteLLM does. It sits between your application and any LLM backend, exposing a single OpenAI-compatible endpoint. Your code stays the same whether you route to Llama 3.1 on Ollama, Claude 3.5 Haiku, or GPT-4o.

What LiteLLM gives you out of the box

  • Unified OpenAI-compatible API — works with every SDK, library and tool that targets the OpenAI API.
  • 100+ provider integrations: Ollama, Anthropic, OpenAI, Azure OpenAI, Mistral, Cohere, Bedrock and more.
  • Virtual API keys with per-key spend limits and rate limits — issue keys to teams and cap monthly costs.
  • Built-in dashboard at /ui with real-time spend analytics, per-model breakdowns and request logs.
  • Load balancing and automatic failover across multiple provider endpoints or model replicas.
  • Apache 2.0 licence — no telemetry, no phoning home, fully air-gap capable when paired with Ollama.

Requirements

At least 1 GB of RAM and Ubuntu 22.04. Docker and the Compose plugin installed. Port 4000 open, or a Caddy/Nginx reverse proxy for HTTPS. That is everything — LiteLLM itself is a Python proxy and needs around 256 MB; PostgreSQL takes most of the remaining RAM budget. For teams with concurrent traffic, 2 GB is more comfortable.

Deploy LiteLLM on a VPS

  1. Provision the VPS

    Order a VPS Solo (2 vCPU, 4 GB, Ubuntu 22.04): LiteLLM needs only 1 GB, and the smallest plan in the catalogue serves twice that. SSH in and install Docker: curl -fsSL https://get.docker.com | sh. The Compose plugin is included.

  2. Generate secrets

    Run openssl rand -hex 32 to generate LITELLM_MASTER_KEY — this unlocks the dashboard and all admin API endpoints. Create /opt/litellm/.env with: LITELLM_MASTER_KEY, UI_USERNAME, UI_PASSWORD, DB_NAME=litellm, DB_USER=litellm, DB_PASSWORD=<strong-password>.

  3. Write docker-compose.yml

    Create /opt/litellm/docker-compose.yml with two services: db (postgres:16-alpine, env vars from .env, volume litellm_db) and litellm (image ghcr.io/berriai/litellm pinned to an explicit version and its @sha256: digest, never main-latest — see "Supply chain" below —, port 4000:4000, depends_on db, DATABASE_URL + LITELLM_MASTER_KEY + UI_USERNAME + UI_PASSWORD, volume litellm_config:/app/data). Run docker compose up -d.

  4. Open the dashboard

    Navigate to http://<vps-ip>:4000/ui and log in with your UI_USERNAME / UI_PASSWORD. You will land on the LiteLLM dashboard. Go to Models to add your first provider. For a local Ollama instance on the same VPS, set model name to ollama/llama3.1 and API base to http://host.docker.internal:11434. For Anthropic, paste your API key and add anthropic/claude-3-5-haiku-20241022.

  5. Test the gateway

    From your workstation, test: curl http://<vps-ip>:4000/v1/chat/completions -H "Authorization: Bearer <LITELLM_MASTER_KEY>" -H 'Content-Type: application/json' -d '{"model": "ollama/llama3.1", "messages": [{"role": "user", "content": "Hello"}]}'. You should get a streamed response back — routed through LiteLLM to your Ollama.

  6. Issue team API keys

    In the dashboard under API Keys, click New Key. Give it a description (e.g. 'frontend-team'), set max_budget: 50 (USD/month) and an optional tpm_limit. Share this key with the team — they point their OpenAI SDK at http://<vps-ip>:4000 and use this key. Spend is tracked per key in the dashboard.

  7. Add HTTPS with Caddy

    Install Caddy: apt install -y caddy. Edit /etc/caddy/Caddyfile: llm.yourdomain.com { reverse_proxy localhost:4000 }. Reload: systemctl reload caddy. Close port 4000 in your firewall — all traffic now flows through 443 with automatic TLS.

  8. Logging in for the first time

    Open the admin interface at /ui: LiteLLM displays a username/password form. Enter admin and the password provided, then create your virtual keys from the dashboard.

Supply chain: pin, verify, rotate keys

An LLM gateway holds the keys of all your providers, which is exactly what a credential stealer is after. The report thread opened on the LiteLLM repository (issue 47826) states that the version 1.107.0 wheels published on PyPI carried three files foreign to the project, cook.pth, cook.py and sitecustomize.py, dropped at the root of site-packages: a credential stealer and a code-execution loader, started on every Python launch. PyPI quarantined that version. The maintainers say they removed the malicious Docker images, and their incident write-up describes a hijack of their release pipeline using developer tokens. The original report said it had not inspected the images; a later update in the same thread found the three files in the main image it checked and recommends treating every image of that version as compromised. This guide cannot know what you pulled on a given day: the four checks below settle it on your own host.

Know what is really running on your host

  1. Read the image digest

    A tag such as main-latest says nothing about what executes. Run docker compose images then docker image inspect --format '{{index .RepoDigests 0}}' ghcr.io/berriai/litellm:main-latest (replace it with the reference from your compose file). Note the sha256: you get and compare it with the one of a version whose provenance you have verified.

  2. Look for the named files, without starting Python

    In a suspect environment every Python start replays the malicious code, pip included. Search with a tool that does not launch it: docker compose exec litellm find / -name 'cook.pth' -o -name 'cook.py' -o -name 'sitecustomize.py' -o -name 'litellm-1.107.0.dist-info' 2>/dev/null (if the image ships find). A lone sitecustomize.py elsewhere on the system can be legitimate; what should alarm you is a cook.pth beside it in site-packages, or a litellm-1.107.0.dist-info folder. Finding these files confirms exposure; finding nothing does not prove the environment is clean.

  3. Read the installed version

    Once nothing looks suspicious, pip show litellm (inside the virtualenv of a direct install) or docker compose exec litellm pip show litellm reports the version. If it is 1.107.0, or if you doubt an image pulled around that time, go straight to key rotation below.

  4. Look for execution traces

    The report lists a lock file /tmp/remote_code_loader.lock, an orphaned python process whose working directory is /, and a marker file /tmp/.pc_once_* written only after a successful upload. Its absence proves nothing: a recreated container wipes /tmp. Use it as a hint, not a verdict.

Pin by digest rather than by tag

A tag is a pointer the publisher can move; the @sha256: digest names exact bytes. In your docker-compose.yml, write image: ghcr.io/berriai/litellm:<version>@sha256:<digest> with an explicit version published before the incident or verified since, never latest, main-latest or main-stable. The maintainers recommend pinning an explicit version, and the issue thread advises the digest. Keep the limit in mind: a digest proves you always pull the same bytes, not who built them. The 1.107.0 images could have been pinned without any alarm. Pinning therefore freezes a decision you made; it does not replace making it.

Update without surprises

  • Read the release notes and the security thread before changing version, instead of letting docker compose pull follow a tag.
  • Fetch the new digest from a test machine, start the gateway without real keys, check its behaviour, and only then carry the digest to production.
  • When the publisher ships provenance attestations, verify them: they say where an image comes from, which a bare digest does not.
  • Keep the previous digest in your configuration repository: rolling back becomes a one-line change.
  • After any security alert, rebuild containers and virtual environments from scratch rather than cleaning in place.

If 1.107.0 ever ran on your host: rotate every key

According to the report, if that version started on a host that reaches the Internet, assume the upload succeeded. The code it describes reads the environment variables of readable processes and, when DATABASE_URL is set, extracts the tables where the gateway stores its providers' credentials and models, that is every upstream key it holds. The rotation therefore covers every secret the host could reach, not only the master key. Revoke at the provider first, then generate the replacement key, then review the last month of consumption for usage you do not recognise.

What to renew

  • Every provider key stored in the gateway: Anthropic, OpenAI, Azure, Mistral, Bedrock and each of the others.
  • LITELLM_MASTER_KEY and every virtual key handed to teams.
  • The database password (DB_PASSWORD and the DATABASE_URL that embeds it), as well as UI_PASSWORD.
  • Any secret in the environment of a process on the host: API tokens, passwords of neighbouring services, .env files.
  • The SSH keys, Git tokens and cloud credentials reachable from the VPS, if the host carried any.
  • After rotation, rebuild the VPS or at least the containers from a verified image: copying fresh secrets into a compromised host exposes them again.

Watch outbound traffic

The report describes an upload to www.litellm.dev, a domain that is not the project's own, and an hourly read of a third-party GitHub repository used as a mailbox for new instructions. The maintainers themselves judge that a network-level restriction is the one that counts, since malicious code ignores application configuration. On a VPS, deny outbound traffic by default and allow only your providers' endpoints; if your firewall filters by address rather than by name, put an outbound proxy with an allowlist in front. Failing that, log the host's DNS lookups and alert on any name the gateway has no reason to call.

Pair LiteLLM with Ollama on the same VPS for a fully air-gapped AI stack. Ollama handles model inference; LiteLLM adds the gateway layer — routing, rate limiting and usage tracking. For privacy-sensitive workloads (legal, finance, medical), this means zero data leaves your infrastructure.

The official documentation

For advanced configuration and options specific to the tool, refer to the official LiteLLM documentation. This guide covers going live on a VPS; the vendor's documentation remains the reference for fine-tuning, major updates and specific use cases.

Deploy your private LLM gateway

Self-host LiteLLM on a ServOrbit VPS — route between Ollama, Anthropic and 100+ providers from a single OpenAI-compatible endpoint, with per-key budget controls.