VPSРейтинг
AI-инфраструктура24 августа 2026 · 21 мин

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. Команда с официального сайта:

от root на чистом Ubuntu
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)"

Теперь создаём файл и подставляем в него полученные значения:

nano /opt/litellm/.env
# ключ администратора шлюза: им создают все остальные ключи.
# Приложениям его НЕ выдавать никогда.
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), а не по названию модели — тогда модель можно поменять, не трогая приложения.

nano /opt/litellm/config.yaml
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
OpenAIopenai/gpt-5.6-lunaOPENAI_API_KEY
Anthropicanthropic/claude-sonnet-5ANTHROPIC_API_KEY
DeepSeekdeepseek/deepseek-chatDEEPSEEK_API_KEY
OpenRouteropenrouter/любая-модельOPENROUTER_API_KEY
Ollama (локально)ollama_chat/qwen3:8bне нужен

Файл 3: docker-compose.yml — как это запускается

nano /opt/litellm/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. Запуск и проверка

из /opt/litellm
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; в Ubuntu это пакет dnsutils
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 на свой домен:

sudo nano /etc/nginx/sites-available/litellm
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 просто не выдаст сертификат.

UFW
sudo ufw allow 'Nginx Full'

Выпускаем сертификат:

HTTPS
sudo certbot --nginx -d llm.example.com

Certbot сам допишет блок с 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. Удобно для временного доступа

Проверим, что ограничения настоящие. Запрос к разрешённой модели проходит, а к запрещённой — нет:

ключ разрешён только для cheap и local
{"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.

фрагмент config.yaml
router_settings:
  num_retries: 2
  cooldown_time: 60
  fallbacks: [{"chat": ["cheap", "local"]}]

Порядок такой: шлюз делает две повторные попытки у основной модели, и только потом переходит к запасной. Модель, которая отвечает ошибками, отправляется в «отстойник» на cooldown_time секунд — новые запросы туда не пойдут, пока она не отлежится. Приложение при этом получает нормальный ответ и про подмену не знает.

Особенно приятно, когда последним в цепочке стоит local: даже если деньги у провайдера кончились или интернет отвалился, сервис продолжает отвечать — хуже, но отвечает.

Фолбэк сильнее ограничений ключа

Мы проверили это отдельно: если у ключа в models разрешена только chat, а фолбэк уводит запрос на другую модель — запрос всё равно уйдёт туда. Список models проверяется на входе, фолбэк срабатывает позже. Вывод простой: не ставьте в цепочку фолбэка дорогую модель, если хотели закрыть к ней доступ.

Локальная модель на том же сервере

Строка local в конфиге ведёт к Ollama, запущенной на самом сервере (не в контейнере). Ставится она одной командой:

установка Ollama и модели
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen3:8b

Дальше — обязательный шаг, без которого связка не заработает. По умолчанию Ollama слушает только 127.0.0.1 и из контейнера не видна. Разрешаем ей слушать все интерфейсы:

sudo systemctl edit ollama
[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:

фрагмент config.yaml
  - 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:

добавить в /opt/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

pip install openai
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

Коротко: порядок действий

  1. VPS с 2 ГБ памяти и 15 ГБ диска, Ubuntu 22.04 или 24.04
  2. curl -fsSL https://get.docker.com | sh
  3. A-запись llm.example.com на IP сервера, проверить через dig +short
  4. Папка /opt/litellm, три файла: .env, config.yaml, docker-compose.yml
  5. Секреты через openssl rand -hex, затем chmod 600 .env
  6. docker compose up -d, подождать полминуты, проверить /health/readiness
  7. Nginx с обязательным proxy_buffering off, затем ufw allow 'Nginx Full' и только после этого certbot --nginx
  8. Зайти в панель /ui логином и паролем из .env — не мастер-ключом
  9. Создать по виртуальному ключу на каждое приложение, с бюджетом и лимитом
  10. Прописать в приложениях новый адрес и ключ, старые ключи провайдеров отозвать
  11. Сохранить .env в надёжном месте: LITELLM_SALT_KEY восстановлению не подлежит

Шлюз пишет в базу строку на каждый запрос, и статистика в панели считается прямо по ним — на NVMe-диске это заметно быстрее, чем на обычном SATA

VPS на NVMe 2026 →