One OpenAI-style address for all your models: LiteLLM proxy on a 2 vCPU VPS with aliases, a key, a fallback and systemd. Real output, no GPU.
An LLM gateway is a small server that speaks one API, the OpenAI chat format, and forwards each request to whichever model you named: a local Ollama model, or a hosted one. Your apps keep one address and one key; swapping a model means editing a YAML file. We ran the LiteLLM proxy on a 2 vCPU VPS with no GPU, put two local models behind it, and tested the key check, a fallback, streaming and the Python client.
In short. Install
litellm[proxy]into a virtualenv, write aconfig.yamlwith amodel_listof aliases, keep the master key in an environment file, and run the proxy from systemd on127.0.0.1:4000. Apps then call/v1/chat/completionswith the alias. It costs about 370 MiB of RAM and roughly 30 ms per request on our box.
Everything below was run on a HIP-HOSTING VPS: Ubuntu 24.04.5, 2 vCPU, 3.8 GB of RAM, no GPU. Ollama 0.40.16 served two small models, qwen2.5:0.5b and llama3.2:1b, and LiteLLM 1.109.1 came from pip. The outputs are copied from the terminal; the master key is replaced with sk-xxxxxxxx.
One app talking to one model needs no gateway: point the app at Ollama and stop. A gateway starts to pay off when something changes often or when more than one program asks for answers.
fast or smart. When you replace the model behind smart, no app changes.If you have not run a local model yet, start with our Ollama on a VPS guide; the gateway sits in front of it.
The proxy is a Python program with a lot of dependencies, so give it its own virtual environment: a private folder of packages that cannot break the system Python. The [proxy] part of the name pulls in the web server pieces; openai is the client library we use later for a test.
It took 50 seconds and 753 MB of disk. That is the first number to remember: this is not a tiny tool. On a small disk, plan for it.
Create the folder and the service user first: mkdir -p /etc/litellm and useradd --system --no-create-home --shell /usr/sbin/nologin litellm. Save the config as /etc/litellm/config.yaml. It has three parts. model_list maps the names your apps use (model_name) to the real model behind them (litellm_params). router_settings holds retry and fallback rules. general_settings holds the master key. We create three aliases: fast and smart point to our two local models, and flaky points to a model that does not exist, to test the fallback on purpose.
model_list: - model_name: fast litellm_params: model: ollama/qwen2.5:0.5b api_base: http://127.0.0.1:11434 - model_name: smart litellm_params: model: ollama/llama3.2:1b api_base: http://127.0.0.1:11434 - model_name: flaky litellm_params: model: ollama/no-such-model api_base: http://127.0.0.1:11434router_settings: num_retries: 0 fallbacks: [{"flaky": ["smart"]}]general_settings: master_key: os.environ/LITELLM_MASTER_KEYLine by line: ollama/qwen2.5:0.5b is "the Ollama provider, this model tag"; api_base is where Ollama listens. num_retries: 0 turns off repeated tries on the same model, so the fallback shows up immediately. The line os.environ/LITELLM_MASTER_KEY tells LiteLLM to read the key from an environment variable, so the secret never sits in the YAML you might paste into a chat or commit to git.
The master key goes into its own file, readable only by root and the service group:
Treat that key as the root password of the gateway: whoever has it can use every model you put behind it.
A service started by hand dies with your SSH session. systemd is the system's service manager: it starts the proxy at boot and restarts it if it crashes. We run it as a dedicated user with no shell and bind it to 127.0.0.1, so nothing outside the server can reach it yet.
[Unit]Description=LiteLLM proxyAfter=network-online.target ollama.service[Service]User=litellmEnvironmentFile=/etc/litellm/envExecStart=/opt/litellm/venv/bin/litellm --config /etc/litellm/config.yaml --host 127.0.0.1 --port 4000Restart=on-failureRestartSec=5[Install]WantedBy=multi-user.targetSave it as /etc/systemd/system/litellm.service, then start it and check that it listens where you expect:
Startup took about ten seconds. The log lists your three aliases, which is the quickest way to confirm the config was read.
The proxy has a liveness address that needs no key, and a model list that does. Calling the list without a key should fail, and it does:
The last line is our own one-line Python filter over the JSON reply, trimmed to the model names. Now a real request. The body is the standard OpenAI chat format: a model name and a list of messages. We shorten the JSON reply with a one-line script to the fields that matter, here and in the fallback test below.
It works. This first request took 5 seconds on two CPU cores, and that includes loading the model into memory. Look at the answer, though: it is wrong. A VPS is not "software where multiple physical servers are managed". A 0.5-billion-parameter model is fluent and often mistaken, and the gateway cannot fix that. The smart alias, the 1B model, answered the same question correctly (7.5 seconds, again a first request that loaded the model): "a virtualized computing environment that provides a dedicated server space for users". Pick the model for the job; the gateway only makes switching cheap.
Because the gateway speaks the OpenAI format, the official openai client works against it. You change base_url and the key; the rest of your code stays as it was.
import osfrom openai import OpenAIc = OpenAI(base_url="http://127.0.0.1:4000/v1", api_key=os.environ["LITELLM_MASTER_KEY"])r = c.chat.completions.create( model="fast", messages=[{"role": "user", "content": "Name one Linux command that shows free disk space. One line."}], max_tokens=40,)print(r.choices[0].message.content)The right command is df, so the model got the main point. The --format flag is invented (the real one is -h). Same lesson as above: never run what a small model suggests without reading it. Our AI agent guide shows how to keep a model's output away from a shell.
This is the feature that justifies the extra moving part. Our flaky alias points at a model Ollama does not have. Called directly, Ollama refuses. Called through the gateway, the request is retried on smart and succeeds:
The caller asked for flaky and got a normal 200 reply, and the model field in the answer tells you who really responded. Note that a normal reply shows the alias you asked for (fast), while this fallback reply showed the real model name. That field is worth logging in your app: a silent fallback is convenient and easy to forget. The same rule works for a real outage, for example a hosted API that is down while a local model is up.
Chat apps print the answer word by word instead of waiting for the whole text. With "stream": true the gateway passes the chunks through as they arrive. We shortened the lines below: real chunks carry more fields.
A gateway is extra work on every request, so we measured it. Memory first: the proxy process held about 370 MiB resident, and systemd reported 359 MiB for the service. With both Ollama models loaded, the server showed 1.0 GiB available out of 3.8 GiB. For speed we sent a one-token request 5 times straight to Ollama and 5 times through the gateway:
After the first, slower request, the gateway added about 20 to 35 ms each time. Against a few seconds per answer that is noise; against a tiny request it is visible. The honest summary: expect about 400 MB of RAM and 750 MB of disk for the proxy alone, so we would not put it on a 1 GB server, and we did not test 1 GB or 2 GB. For the models themselves, our measured speed was 22.5 tokens per second for the 0.5B model and 10.0 for the 1B model on these two cores.
{"error":{"message":"No connected db.","type":"no_db_connection",...}} [400]. LiteLLM looks up unknown keys in a database as "virtual keys", and we did not connect one. If your app treats only 401 as "bad key", handle this case too.model=nope returned "Invalid model name passed in model=nope. Call /v1/models to view available models for your key." Good: typos in model names are easy to spot.systemctl restart litellm the health check answered again within 20 seconds, and the unit is enabled for the next boot.A hosted model is one more entry in model_list, under a new alias such as hosted. Reusing an existing alias would make LiteLLM spread requests between the two models. The sketch below follows the project's documented format; we did not run it, because it needs your own account key, so check the model name against your provider before you rely on it:
- model_name: hosted litellm_params: model: openai/gpt-4o-mini # not run here: needs your key api_key: os.environ/OPENAI_API_KEYWith two entries sharing one model_name, LiteLLM can spread requests across them, but we did not test that either. We also did not set up a database, so per-user keys and spending limits are untested, and we did not use the Admin UI or Docker. Everything in the sections above is what we actually ran.
To make the gateway reachable from other machines, do not just open port 4000. Put a web server with HTTPS in front of it, as in our nginx and ufw guide, and allow only the addresses that need it.
A server between your apps and your language models. Apps send every request in one format to one address; the gateway picks the real model by name, applies rules such as keys and fallbacks, and returns the answer in the same format. LiteLLM's proxy is one open-source way to do it.
No. LiteLLM itself only forwards requests and uses CPU and memory, about 370 MiB of RAM in our test. A GPU matters only for the models behind it, and our two small ones ran on CPU.
We installed it with pip and used it without paying anything. The project also sells some features separately, so read its current licence page before you build a business on a specific feature.
Set the app's API base URL to http://127.0.0.1:4000/v1, use the master key as the API key and use an alias such as fast as the model name. Any client that can talk to the OpenAI API format works, as the Python example above shows.
Yes, that is the usual setup: a local model for private, cheap requests and a hosted one for hard questions, with a fallback between them. We tested the local models and the fallback; the hosted entry above is untested.
127.0.0.1, with the master key in a root-owned environment file and not in the YAML.model field so you notice it.Not tested here: hosted APIs, Postgres and virtual keys, balancing across several deployments, 1 GB and 2 GB servers, other systems than Ubuntu 24.04. We ran all of it on a HIP-HOSTING VPS with 2 vCPU and 4 GB of RAM; if you want one for this, see hip.hosting and check the order form for current availability.