LiteLLM на VPS: один ключ и один адрес для всех нейросетей
Ключи от платных нейросетей имеют свойство расползаться: по одному в каждом приложении, ещё пара в блокноте, один в чужом ноутбуке. Общий счёт приходит один, и понять, кто именно его сжёг, невозможно. LiteLLM ставит между приложениями и провайдерами одну дверь: настоящие ключи остаются за ней, наружу выдаются отдельные пропуска с бюджетом, а поменять модель для всех сразу можно правкой одной строки.
Проблема, которую видно не сразу
Первое приложение с нейросетью выглядит просто: получили ключ, положили в .env, написали три строки кода. Второе — то же самое. К четвёртому картина меняется, и меняется незаметно.
Ключи размножились
Один и тот же платный ключ лежит в четырёх местах: на двух серверах, в CI и у коллеги на ноутбуке. Если утёк любой из них — менять придётся везде одновременно, и что-нибудь обязательно забудется.
Счёт общий, виноватых нет
Провайдер присылает одну сумму за месяц. Какое приложение съело половину, а какое почти ничего — из этого счёта не видно. Ставить лимит не на что: он бывает только на весь аккаунт целиком.
Провайдер прилёг — встало всё
Полчаса ошибок 429 и 500 у одного провайдера роняют разом все приложения, потому что запасного адреса не прописано ни в одном.
Смена модели — это релиз
Вышла модель дешевле и лучше. Чтобы перейти на неё, надо править код в четырёх репозиториях, тестировать и выкатывать. Обычно этого просто не делают.
Для тех, кто работает из России, добавляется пятая: доступ к зарубежным API надо как-то организовать. И если приложений несколько, этот вопрос приходится решать в каждом отдельно — свой прокси, свои настройки, свои поломки.
Идея шлюза в одном предложении
Все настоящие ключи живут в одном месте на вашем сервере, а приложения обращаются к одному адресу в привычном формате OpenAI — с личным ключом, своим бюджетом и записью каждого запроса в журнал.
Приложениям при этом ничего не надо переписывать. Почти все библиотеки для работы с нейросетями умеют говорить в формате OpenAI, и почти во всех можно подменить адрес сервера. Меняются две строки: base_url и api_key.
Что такое LiteLLM
LiteLLM — это программа-посредник (её называют шлюзом или gateway), которая принимает запросы в формате OpenAI и пересылает их в тот сервис, который вы указали в конфиге: OpenAI, Anthropic, Google, DeepSeek, агрегатор вроде OpenRouter, российский YandexGPT или локальную модель в Ollama на этом же сервере. Для приложения все они выглядят одинаково — как обычный OpenAI.
Проект живёт с 2023 года и стал фактическим стандартом в этой нише: больше 57 тысяч звёзд на GitHub, стабильные версии выходят примерно раз в неделю. На момент написания статьи актуальна 1.98.0 от 23 августа 2026 года — её и будем ставить.
Бесплатно или нет
Основной код открыт под лицензией MIT, платная только папка enterprise/. В бесплатной версии есть всё, ради чего шлюз обычно ставят: веб-панель, виртуальные ключи, бюджеты, лимиты, команды, учёт расходов, фолбэк и логи. Подписка нужна для корпоративных вещей — вход через SSO больше чем для пяти человек, метрики в Prometheus, логи в Datadog и S3, автоматическая ротация ключей.
Что вы получаете сразу после установки
- Один адрес вместо пяти. Все приложения ходят на
https://llm.example.com/v1, какой провайдер за ним — их не касается. - Виртуальные ключи. Каждому приложению и человеку свой ключ: список разрешённых моделей, бюджет в долларах, ограничение запросов в минуту, срок действия. Ключ можно отозвать, не трогая остальные.
- Учёт до цента. Шлюз считает стоимость каждого запроса и показывает, кто и сколько потратил — по ключу, по модели, по человеку.
- Фолбэк. Провайдер ответил ошибкой — запрос автоматически уходит запасной модели. Приложение об этом не узнает.
- Смена модели без релиза. Имя
chatв конфиге можно завтра перевесить на другую модель, и все приложения переедут молча.
Что понадобится
Шлюз лёгкий: он ничего не считает сам, только пересылает запросы. Цифры ниже — замеры на реальной установке версии 1.98 в покое, а не оценки из документации.
| Память | LiteLLM 730–760 МБ + PostgreSQL 45–65 МБ. Берите VPS с 2 ГБ — на 1 ГБ будет впритык |
| Диск | Образ шлюза 1,19 ГБ, образ базы 294 МБ, сама база растёт медленно. 15 ГБ достаточно |
| Процессор | Одного ядра хватает: вся работа идёт на стороне провайдера |
| Система | Ubuntu 22.04 или 24.04. Docker поставим на первом шаге |
| Домен | Поддомен вида llm.example.com с A-записью на IP сервера |
Если планируете держать рядом ещё и локальную модель через Ollama, память считайте отдельно: под модель на 8 миллиардов параметров нужно ещё 6–8 ГБ.
Шаг 1. Docker
Всё ставится в контейнерах, поэтому начинаем с Docker. Команда с официального сайта:
curl -fsSL https://get.docker.com | shПроверяем, что всё встало:
docker --version && docker compose versionОбе команды должны вывести номер версии. Если про Docker вы слышите впервые — у нас есть отдельная статья с объяснением основ.
Шаг 2. Три файла
Вся установка — это три файла в одной папке. Создаём её:
mkdir -p /opt/litellm && cd /opt/litellmФайл 1: .env — секреты
Здесь лежат пароли и ключи. Сначала сгенерируем случайные значения — не придумывайте их руками:
echo "мастер-ключ: sk-$(openssl rand -hex 16)"
echo "salt-ключ: $(openssl rand -hex 16)"
echo "пароль базы: $(openssl rand -hex 12)"
echo "пароль панели: $(openssl rand -hex 8)"Теперь создаём файл и подставляем в него полученные значения:
# ключ администратора шлюза: им создают все остальные ключи.
# Приложениям его НЕ выдавать никогда.
LITELLM_MASTER_KEY=sk-ЗАМЕНИТЕ-НА-СГЕНЕРИРОВАННЫЙ
# этим ключом шифруются данные провайдеров в базе.
# Задать один раз и больше не менять — иначе расшифровать их не получится.
LITELLM_SALT_KEY=ЗАМЕНИТЕ-НА-СГЕНЕРИРОВАННЫЙ
# пароль базы данных
POSTGRES_PASSWORD=ЗАМЕНИТЕ-НА-СГЕНЕРИРОВАННЫЙ
# логин и пароль от веб-панели
UI_USERNAME=admin
UI_PASSWORD=ЗАМЕНИТЕ-НА-СГЕНЕРИРОВАННЫЙ
# ключи провайдеров — те, что вы получили в их личных кабинетах
OPENROUTER_API_KEY=sk-or-v1-ваш-ключПароли здесь намеренно шестнадцатеричные, без спецсимволов. Пароль базы попадёт внутрь строки подключения вида postgresql://litellm:ПАРОЛЬ@db:5432/litellm, и символы вроде @, # или / её сломают. Придумывать пароль руками не надо.
Закрываем файл от посторонних — в нём все ваши платные ключи:
chmod 600 /opt/litellm/.envПро LITELLM_SALT_KEY — прочитайте сейчас, а не потом
Этим ключом шифруются настройки провайдеров, которые вы добавите через веб-панель. Если его поменять, шлюз запустится как ни в чём не бывало, но добавленные через панель модели просто исчезнут из списка — без ошибки и без предупреждения. Мы это специально проверили: старое значение возвращает всё на место, но догадаться до причины с нуля почти невозможно. Задайте его один раз и сохраните вместе с бэкапом.
Файл 2: config.yaml — какие модели раздаём
Это главный файл. Слева — имя, которое увидят приложения, справа — что за ним стоит на самом деле. Имена придумываете вы: удобно называть их по роли (chat, cheap, local), а не по названию модели — тогда модель можно поменять, не трогая приложения.
model_list:
# основная модель
- model_name: chat
litellm_params:
model: openrouter/anthropic/claude-sonnet-5
api_key: os.environ/OPENROUTER_API_KEY
# дешёвая и быстрая — и она же первая запасная
- model_name: cheap
litellm_params:
model: openrouter/google/gemini-2.5-flash
api_key: os.environ/OPENROUTER_API_KEY
# локальная модель на этом же сервере (см. раздел про Ollama)
- model_name: local
litellm_params:
model: ollama_chat/qwen3:8b
api_base: http://host.docker.internal:11434
litellm_settings:
# молча выбрасывать параметры, которых нет у модели,
# вместо того чтобы падать с ошибкой
drop_params: true
router_settings:
# две попытки, потом переход к запасной модели
num_retries: 2
# сколько секунд не трогать модель, которая отвечает ошибками
cooldown_time: 60
# если "chat" не отвечает — пробуем "cheap", потом "local"
fallbacks: [{"chat": ["cheap", "local"]}]Запись os.environ/OPENROUTER_API_KEY означает «взять значение из файла .env». Так настоящий ключ не попадает в конфиг, который вы, скорее всего, положите в git.
Локальная модель нужна не всем. Если Ollama ставить не планируете — удалите блок local целиком и уберите его из строки fallbacks, иначе последним запасным вариантом окажется несуществующий адрес.
В примере всё идёт через OpenRouter — агрегатор, у которого один ключ даёт доступ к моделям всех крупных производителей. Это удобно для начала. Подключить провайдера напрямую тоже просто, префикс перед именем модели указывает, куда идти:
| Провайдер | Строка model | Ключ в .env |
|---|---|---|
| OpenAI | openai/gpt-5.6-luna | OPENAI_API_KEY |
| Anthropic | anthropic/claude-sonnet-5 | ANTHROPIC_API_KEY |
| DeepSeek | deepseek/deepseek-chat | DEEPSEEK_API_KEY |
| OpenRouter | openrouter/любая-модель | OPENROUTER_API_KEY |
| Ollama (локально) | ollama_chat/qwen3:8b | не нужен |
Файл 3: docker-compose.yml — как это запускается
services:
litellm:
image: ghcr.io/berriai/litellm-database:v1.98.0
container_name: litellm
restart: unless-stopped
ports:
# только локально: наружу шлюз пустит Nginx
- "127.0.0.1:4000:4000"
volumes:
- ./config.yaml:/app/config.yaml:ro
command: ["--config", "/app/config.yaml", "--port", "4000"]
env_file: .env
environment:
DATABASE_URL: postgresql://litellm:${POSTGRES_PASSWORD}@db:5432/litellm
STORE_MODEL_IN_DB: "True"
extra_hosts:
# чтобы контейнер видел Ollama, запущенную на самом сервере
- "host.docker.internal:host-gateway"
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
container_name: litellm-db
restart: unless-stopped
environment:
POSTGRES_USER: litellm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: litellm
healthcheck:
test: ["CMD-SHELL", "pg_isready -U litellm"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- litellm_pgdata:/var/lib/postgresql/data
volumes:
litellm_pgdata:Три места, на которые стоит обратить внимание:
127.0.0.1:4000:4000— порт виден только с самого сервера. Без этого Docker открыл бы его всему интернету в обход брандмауэра UFW, и любой желающий получил бы вашу веб-панель. Наружу шлюз выпустит Nginx на следующем шаге.env_file: .env— все переменные из файла попадают внутрь контейнера. Именно поэтому работает записьos.environ/OPENROUTER_API_KEYв конфиге: добавили нового провайдера в.env— и он сразу доступен.- База в отдельном контейнере не случайна. Без неё шлюз тоже работает, но виртуальные ключи, бюджеты и история расходов существуют только с базой.
STORE_MODEL_IN_DBразрешает добавлять модели ещё и через веб-панель (раздел Models + Endpoints). Такие модели складываются с теми, что описаны вconfig.yaml, и переживают перезапуск. Мы всё же ведём основной список в конфиге: его видно целиком одним файлом и можно хранить в git.
Шаг 3. Запуск и проверка
docker compose up -dПервый запуск занимает время: надо скачать полтора гигабайта образов, поднять базу и создать в ней таблицы. На нашем тесте от команды до готовности прошло 26 секунд после скачивания образов, последующие перезапуски — 18 секунд. Ждём и проверяем:
curl http://127.0.0.1:4000/health/readinessПравильный ответ:
{"status":"healthy","db":"connected"}Если ответа нет вообще — смотрим, что случилось:
docker compose logs litellm --tail 40Несколько жёлтых строк WARNING: register_model с длинными наборами букв и цифр в логах — это норма, а не поломка. Шлюз всего лишь сообщает, что не знает цену кэширования для ваших моделей. На работу это не влияет.
Теперь спросим у шлюза, какие модели он раздаёт. Подставьте свой мастер-ключ:
curl http://127.0.0.1:4000/v1/models \
-H "Authorization: Bearer ВАШ-МАСТЕР-КЛЮЧ"В ответе должны быть все имена из config.yaml: chat, cheap, local. Если какого-то нет — в конфиге ошибка, ищите её в логах.
Шаг 4. Домен, Nginx и HTTPS
Сначала заведите у своего DNS-провайдера A-запись llm.example.com на IP сервера и дождитесь, пока она разойдётся, — иначе certbot не выдаст сертификат:
dig +short llm.example.com @8.8.8.8Должен вывестись IP вашего сервера. Ставим веб-сервер:
sudo apt update && sudo apt install -y nginx certbot python3-certbot-nginxСоздаём конфиг сайта. Замените llm.example.com на свой домен:
server {
listen 80;
server_name llm.example.com;
# запросы с картинками и длинным контекстом бывают большими
client_max_body_size 25m;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# ответ модели идёт словом за словом: буферизацию выключаем,
# иначе текст в чате появится одним куском в самом конце
proxy_buffering off;
proxy_cache off;
# модель может думать несколько минут — стандартной минуты мало
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}Включаем сайт и проверяем синтаксис:
sudo ln -s /etc/nginx/sites-available/litellm /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginxОткрываем порты в брандмауэре — обязательно довыпуска сертификата: Let's Encrypt проверяет владение доменом, стучась на 80-й порт снаружи. Если он закрыт, certbot просто не выдаст сертификат.
sudo ufw allow 'Nginx Full'Выпускаем сертификат:
sudo certbot --nginx -d llm.example.comCertbot сам допишет блок с 443 портом и настроит редирект с HTTP. Проверяем результат снаружи:
curl https://llm.example.com/health/readinessТот же ответ {"status":"healthy","db":"connected"}, но уже снаружи, означает, что шлюз доступен из интернета по HTTPS. Веб-панель теперь живёт по адресу https://llm.example.com/ui — заходите логином и паролем из UI_USERNAME и UI_PASSWORD.
Мастер-ключ паролем от панели не работает
В документации написано, что войти можно как admin с мастер-ключом вместо пароля. Это верно только пока не заданы UI_USERNAME и UI_PASSWORD. Мы их задали в .env, поэтому вход только по ним — мастер-ключ панель отклонит.
Виртуальные ключи: главное, ради чего всё затевалось
Мастер-ключ — как ключ от сейфа: им можно всё, включая создание других ключей. Ни одно приложение его получать не должно. Вместо этого каждому выдаётся виртуальный ключ: он тоже начинается с sk- и работает точно так же, но у него есть список разрешённых моделей, бюджет и лимит скорости.
Проще всего создать ключ в панели: Virtual Keys → Create New Key. Но через командную строку нагляднее видно, что именно настраивается:
curl http://127.0.0.1:4000/key/generate \
-H "Authorization: Bearer ВАШ-МАСТЕР-КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"key_alias": "telegram-bot",
"models": ["cheap", "local"],
"max_budget": 10,
"budget_duration": "30d",
"rpm_limit": 30
}'В ответе придёт строка "key": "sk-..." — это и есть ключ для приложения. Показывается он один раз, потом только удаляется и создаётся заново. Что означают поля:
| key_alias | Понятное имя. Видно в панели и в логах — иначе через месяц не вспомните, чей это ключ |
| models | Список разрешённых моделей. Попытка обратиться к другой вернёт ошибку 403 |
| max_budget | Потолок расходов в долларах. Достигнут — ключ перестаёт работать |
| budget_duration | Через какой срок счётчик обнуляется: 30d, 7d, 24h |
| rpm_limit | Запросов в минуту. Защита от кода, который зациклился |
| duration | Срок жизни самого ключа, например 90d. Удобно для временного доступа |
Проверим, что ограничения настоящие. Запрос к разрешённой модели проходит, а к запрещённой — нет:
{"error":{"message":"key not allowed to access model.
This key can only access models=['cheap', 'local']. Tried to access chat",
"type":"key_model_access_denied","param":"model","code":"403"}}И бюджет тоже настоящий. Когда потрачено больше, чем разрешено, дальнейшие запросы отклоняются:
{"error":{"message":"Budget has been exceeded! Key=telegram-bot (sk-...a7c2)
Current cost: 10.0012, Max budget: 10.0"}}Посмотреть, сколько уже потрачено:
curl "http://127.0.0.1:4000/key/info?key=sk-КЛЮЧ-ПРИЛОЖЕНИЯ" \
-H "Authorization: Bearer ВАШ-МАСТЕР-КЛЮЧ"А если ключ утёк — отзываем его одной командой, остальные при этом не трогаются:
curl -X POST http://127.0.0.1:4000/key/delete \
-H "Authorization: Bearer ВАШ-МАСТЕР-КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{"keys": ["sk-КЛЮЧ-ПРИЛОЖЕНИЯ"]}'В ответ придёт {"deleted_keys":["sk-..."]}, и со следующей же секунды этот ключ получает 401. Список всех выданных ключей — /key/list, но удобнее смотреть его в панели, там сразу видны расходы по каждому.
Расход появляется не мгновенно
Шлюз копит записи и пишет их в базу пачками. Поэтому сразу после запроса /key/info ещё покажет старую сумму — на нашем тесте она обновилась примерно через 15–20 секунд. Это не поломка. На проверку бюджета задержка не влияет: лимит срабатывает и так.
Когда провайдер лёг
Строка fallbacks в конфиге, которую мы уже написали, читается так: если chat ответил ошибкой — попробовать cheap, а если и он молчит — local.
router_settings:
num_retries: 2
cooldown_time: 60
fallbacks: [{"chat": ["cheap", "local"]}]Порядок такой: шлюз делает две повторные попытки у основной модели, и только потом переходит к запасной. Модель, которая отвечает ошибками, отправляется в «отстойник» на cooldown_time секунд — новые запросы туда не пойдут, пока она не отлежится. Приложение при этом получает нормальный ответ и про подмену не знает.
Особенно приятно, когда последним в цепочке стоит local: даже если деньги у провайдера кончились или интернет отвалился, сервис продолжает отвечать — хуже, но отвечает.
Фолбэк сильнее ограничений ключа
Мы проверили это отдельно: если у ключа в models разрешена только chat, а фолбэк уводит запрос на другую модель — запрос всё равно уйдёт туда. Список models проверяется на входе, фолбэк срабатывает позже. Вывод простой: не ставьте в цепочку фолбэка дорогую модель, если хотели закрыть к ней доступ.
Локальная модель на том же сервере
Строка local в конфиге ведёт к Ollama, запущенной на самом сервере (не в контейнере). Ставится она одной командой:
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8bДальше — обязательный шаг, без которого связка не заработает. По умолчанию Ollama слушает только 127.0.0.1 и из контейнера не видна. Разрешаем ей слушать все интерфейсы:
[Service]
Environment="OLLAMA_HOST=0.0.0.0"sudo systemctl daemon-reload && sudo systemctl restart ollamaСразу закройте порт 11434 снаружи
После OLLAMA_HOST=0.0.0.0 Ollama доступна на внешнем интерфейсе, а аутентификации у неё нет вообще. Убедитесь, что UFW включён и порт 11434 в нём не открыт: sudo ufw status. Контейнеру LiteLLM это не мешает — он ходит на внутренний адрес Docker.
Адрес host.docker.internal в конфиге — это способ контейнера обратиться к самому серверу. Работает он благодаря строке extra_hosts в docker-compose.yml; без неё имя не разрешится. Проверяем, что модель отвечает через шлюз:
curl http://127.0.0.1:4000/v1/chat/completions \
-H "Authorization: Bearer ВАШ-МАСТЕР-КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{"model":"local","messages":[{"role":"user","content":"Привет"}]}'Подробности про выбор модели и требования к памяти — в статье про Ollama. Коротко: модель на 8 миллиардов параметров требует 6–8 ГБ памяти сверх шлюза, а без видеокарты отвечает десятки секунд.
Если вы в России
Здесь у шлюза есть неочевидное преимущество: вопросы оплаты и доступности решаются один раз в одном месте. Приложения не знают ни про прокси, ни про то, чей ключ используется, — они просто ходят на ваш адрес.
Вариант 1: российская модель
YandexGPT оплачивается рублями и отдаёт OpenAI-совместимый адрес, поэтому подключается как обычный провайдер. Понадобятся идентификатор каталога и API-ключ сервисного аккаунта из консоли Yandex Cloud:
- model_name: yandex
litellm_params:
model: openai/gpt://b1gxxxxxxxxxxxxxxxxx/yandexgpt/latest
api_base: https://llm.api.cloud.yandex.net/v1
api_key: os.environ/YANDEX_API_KEYПрефикс openai/ здесь означает не OpenAI, а «говори в формате OpenAI по адресу из api_base». Всё, что идёт после него, уходит провайдеру как имя модели без изменений — мы это проверили отдельно. Тем же способом подключается любой сервис-посредник: меняются только api_base и имя модели.
Если в ответ приходит ошибка 401, дело почти всегда в ключе: нужен именно постоянный API-ключ сервисного аккаунта, а не IAM-токен — тот живёт 12 часов и на следующий день перестанет работать.
Вариант 2: зарубежный провайдер через свой прокси
Если платить зарубежному провайдеру вы можете, а достучаться до него с российского сервера — нет, заверните исходящие запросы шлюза через свой сервер за границей. LiteLLM понимает стандартные переменные окружения, так что достаточно дописать три строки в .env:
HTTP_PROXY=http://user:password@203.0.113.10:3128
HTTPS_PROXY=http://user:password@203.0.113.10:3128
NO_PROXY=db,localhost,127.0.0.1,host.docker.internalСтрока NO_PROXY обязательна: без неё запросы к локальной Ollama тоже уйдут в прокси за границу и, разумеется, никуда не придут. После правки — docker compose up -d (перезапуск через restart новые переменные не подхватит).
Прокси только HTTP, не SOCKS5
Обычный HTTP-прокси мы проверили на живой установке: запросы к провайдеру действительно уходят через него. А вот socks5:// не заработает — библиотеки для SOCKS внутри образа просто нет. Если у вас есть только SOCKS5, поднимите рядом переходник: например, добавьте HTTP-вход в конфиг Xray.
Вариант 3: посредник с рублёвой оплатой
Сервисов, которые принимают оплату российской картой и отдают OpenAI-совместимый адрес, довольно много. С точки зрения LiteLLM это ровно тот же случай, что и Яндекс: префикс openai/, свой api_base, свой ключ. Никакой особой поддержки не требуется.
Подключаем приложения
Для приложения ваш шлюз — обычный OpenAI. Везде меняются две вещи: адрес и ключ.
Python
from openai import OpenAI
client = OpenAI(
base_url="https://llm.example.com/v1",
api_key="sk-ВИРТУАЛЬНЫЙ-КЛЮЧ",
)
answer = client.chat.completions.create(
model="chat",
messages=[{"role": "user", "content": "Привет"}],
)
print(answer.choices[0].message.content)Open WebUI
Путь такой: Admin Panel → Settings → Connections, блок Manage OpenAI API Connections. В поле API Base URL пишем https://llm.example.com/v1, в API Key — виртуальный ключ. В выпадающем списке моделей появятся ровно те имена, которые разрешены этому ключу: шлюз отдаёт каждому свой список.
Всё остальное
Библиотеки на JavaScript, Go и Rust, редакторы кода с поддержкой своего эндпоинта, n8n, Dify — везде ищите поля «OpenAI API base URL» и «API key». Быстрая проверка из терминала:
curl https://llm.example.com/v1/chat/completions \
-H "Authorization: Bearer sk-ВИРТУАЛЬНЫЙ-КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{"model":"chat","messages":[{"role":"user","content":"Привет"}]}'Безопасность
Шлюз хранит все ваши платные ключи, а его собственный мастер-ключ открывает доступ ко всему сразу. Относиться к нему стоит как к паролю от банка.
Мастер-ключ — только для администрирования
Им создают виртуальные ключи и смотрят статистику. В приложения, скрипты и переписку он попадать не должен. Если он всё-таки утёк — поменяйте LITELLM_MASTER_KEY в .env и выполните docker compose up -d: старый ключ перестаёт работать сразу же, а виртуальные ключи, бюджеты и история при этом не страдают. Мы это проверили.
Порт наружу не открывать
В нашем docker-compose.yml порт привязан к 127.0.0.1 — это важно. Docker умеет пробивать правила UFW, поэтому строка вида "4000:4000" открыла бы панель всему интернету, даже при включённом брандмауэре.
У каждого приложения свой ключ с бюджетом
Не выдавайте один ключ на всех. Отдельные ключи позволяют увидеть, кто сколько тратит, и отозвать один, не ломая остальные.
Файл .env — самое ценное на сервере
Права 600, отдельный бэкап в надёжном месте. В нём мастер-ключ, salt-ключ и ключи всех провайдеров. Восстановить базу без salt-ключа получится, но настройки провайдеров из неё уже не расшифруются.
Базовая защита сервера
SSH по ключу вместо пароля, UFW с открытыми 22, 80 и 443, Fail2Ban. Всё это описано в нашей статье про безопасность VPS.
Бэкап и обновление
Бэкап состоит из двух частей: три файла из /opt/litellm и дамп базы с ключами и расходами.
cd /opt/litellm
docker compose exec -T db pg_dump -U litellm litellm > ~/litellm-$(date +%F).sqlОбновление — правка одной строки с версией в docker-compose.yml и две команды. Версию лучше указывать явно, а не latest: релизы выходят часто, и внезапный переезд на новую версию посреди рабочего дня никому не нужен.
cd /opt/litellm
docker compose pull && docker compose up -dЧастые проблемы
Контейнер запускается и сразу падает, и так по кругу
В девяти случаях из десяти это опечатка в config.yaml: YAML чувствителен к отступам, и лишний пробел ломает файл целиком. Точное место указано в логах — docker compose logs litellm --tail 40, ищите строку со словом ParserError и номером строки. Частая ошибка — дописать новую модель в конец файла: она должна быть внутри model_list, а не после других разделов.
Поменяли пароль базы — и всё перестало работать
POSTGRES_PASSWORD задаётся при самом первом запуске и запоминается внутри базы навсегда. Если поменять его в .env позже, шлюз начнёт стучаться с новым паролем, а база будет ждать старый — /health/readiness перестанет отвечать. Верните прежнее значение. Менять пароль базы по-настоящему стоит только вместе с удалением тома и потерей истории расходов, так что проще этого не делать.
Модели, добавленные через панель, пропали
Значит, изменился LITELLM_SALT_KEY — например, вы перевыпустили .env и сгенерировали его заново. Расшифровать настройки провайдеров старым способом шлюз больше не может и просто их не показывает. Верните прежнее значение и перезапустите — модели вернутся. Если старое значение утеряно, придётся добавить провайдеров заново; виртуальные ключи и история расходов при этом не пострадают.
Ошибка «Invalid model name passed in model=...»
Приложение просит модель по имени, которого шлюз не знает. Проверьте список: curl http://127.0.0.1:4000/v1/models -H "Authorization: Bearer МАСТЕР-КЛЮЧ". Обращаться нужно к имени из левой колонки config.yaml (chat), а не к полному названию модели у провайдера.
Ответ в чате появляется целиком в самом конце
Текст должен печататься словом за словом, а вместо этого полминуты тишины и затем всё сразу. Виноват Nginx: он копит ответ в буфере. В конфиге сайта должна быть строка proxy_buffering off; — проверьте, что она есть и что после правки вы сделали sudo systemctl reload nginx.
Модель «local» не отвечает, хотя Ollama работает
Почти всегда забыт OLLAMA_HOST=0.0.0.0: Ollama слушает только 127.0.0.1 и из контейнера не видна. Проверьте командой ss -tlnp | grep 11434 — в выводе должно быть 0.0.0.0:11434, а не 127.0.0.1:11434. Вторая по частоте причина — забытая строка extra_hosts в docker-compose.yml.
Расходы в панели показывают ноль
Две причины. Первая — задержка: записи пишутся в базу пачками, подождите полминуты. Вторая — шлюз не знает цену модели. Для моделей у известных провайдеров цены встроены, а для своего посредника их нужно указать вручную в config.yaml — стоимость одного токена в долларах:
- model_name: posrednik
litellm_params:
model: openai/gpt-4o
api_base: https://api.посредник.ru/v1
api_key: os.environ/POSREDNIK_API_KEY
model_info:
input_cost_per_token: 0.0000025
output_cost_per_token: 0.00001Коротко: порядок действий
- VPS с 2 ГБ памяти и 15 ГБ диска, Ubuntu 22.04 или 24.04
curl -fsSL https://get.docker.com | sh- A-запись
llm.example.comна IP сервера, проверить черезdig +short - Папка
/opt/litellm, три файла:.env,config.yaml,docker-compose.yml - Секреты через
openssl rand -hex, затемchmod 600 .env docker compose up -d, подождать полминуты, проверить/health/readiness- Nginx с обязательным
proxy_buffering off, затемufw allow 'Nginx Full'и только после этогоcertbot --nginx - Зайти в панель
/uiлогином и паролем из.env— не мастер-ключом - Создать по виртуальному ключу на каждое приложение, с бюджетом и лимитом
- Прописать в приложениях новый адрес и ключ, старые ключи провайдеров отозвать
- Сохранить
.envв надёжном месте:LITELLM_SALT_KEYвосстановлению не подлежит
Шлюз пишет в базу строку на каждый запрос, и статистика в панели считается прямо по ним — на NVMe-диске это заметно быстрее, чем на обычном SATA
VPS на NVMe 2026 →Смотрите также: