Ollama на своём сервере: пять граблей при запуске локальной LLM без видеокарты

Мы развернули Ollama на своём железе — в контуре работающего Telegram-бота, без видеокарты. Установка «одной командой» не сработала. Пять граблей, которых нет в официальной инструкции.

Мы развернули Ollama на своём железе — не «попробовать на ноутбуке», а в контуре работающего Telegram-бота. У бота переключаемые LLM-провайдеры, и сейчас на все запросы отвечает именно Ollama: облачный провайдер отключён. Оговорюсь сразу, чтобы не набивать себе цену: это не лабораторный стенд, но и не высоконагруженный прод — один потребитель и низкая критичность сервиса. Инструкция на сайте проекта обещает установку одной командой. У нас эта команда не сработала вообще, и дальше стало интереснее.

Ниже — то, что мы узнали, пока доводили это до зелёного. Пересказа документации не будет: она хорошая, она есть, и написана лучше, чем это сделал бы я. Будет ровно то, чего в ней нет.

Что именно у нас работает: конфигурация и системные требования Ollama на Ubuntu 24.04

Чтобы было понятно, к чему относятся цифры дальше.

  • Отдельная виртуальная машина на Proxmox: 8 vCPU, 16 ГБ RAM, 40 ГБ диска, Ubuntu 24.04.
  • Видеокарты нет. Совсем. Инференс идёт на процессоре — это важная рамка для всего текста.
  • Ollama v0.30.9, поставлена 17 июня 2026 года, работает как systemd-юнит от системного пользователя, без Docker.
  • Модель — qwen2.5:7b в квантизации Q4 (сжатие весов до 4 бит, чтобы модель занимала меньше памяти ценой небольшой потери качества).
  • Потребитель ровно один: наш бот. Он ходит по внутренней сети на OpenAI-совместимый эндпоинт Ollama — путь /v1 на порту 11434, то есть тот же протокол, что у облачных провайдеров.
  • Всё развёртывание описано Ansible-ролью — то есть повторяемо, а не «настроил руками и забыл».

Речь про Linux-сервер. Про Ollama на Windows-десктопе здесь не будет ничего: мы так не работаем, и придумывать не станем.

Грабля 1. Официальный установщик уходит в 404, и он в этом не виноват

Установка Ollama выглядит так: скачать скрипт с сайта проекта и выполнить его. Скрипт сам определит архитектуру, заберёт архив с бинарником и разложит по местам.

У нас он падал. Не с внятной ошибкой, а хуже: скрипт скачивал не тот файл и заканчивался сообщением о 404.

Что выяснилось при разборе (проверено 17 июня 2026):

  1. Архив с бинарником лежит не на сайте Ollama, а на релизном CDN GitHub — release-assets.githubusercontent.com. Из нашей сети этот хост не отдаёт тело ответа: соединение устанавливается, а данные не идут — 0 байт за 40 секунд, потом таймаут.
  2. Формат архива у Ollama сменился со старого .tgz на .tar.zst. Установщик, не сумев забрать новый архив, честно откатывался на запасной вариант со старым именем файла — а того на CDN уже нет. Отсюда и 404.

То есть видимая ошибка (404 по .tgz) не имела к причине никакого отношения. Причина — блокировка релизного CDN GitHub, а не отсутствие файла. Мы потеряли на этом время именно потому, что поверили сообщению об ошибке.

Отдельно интересно: реестр моделей registry.ollama.ai из той же сети доступен нормально. Слой модели на 4,68 ГБ качался со скоростью около 2 МБ/с — небыстро, но без единого обрыва. Блокируется именно раздача бинарников, не модели.

Что сделали. Перестали пользоваться установщиком. Архив .tar.zst скачивается на управляющую машину (там, где сеть не режется), копируется на сервер и распаковывается в /usr/local связкой zstd -d -c … | tar -xf - -C /usr/local. Пакет zstd для этого нужно поставить заранее — на чистой Ubuntu его нет. Дальше — свой systemd-юнит, системный пользователь, systemctl enable. Всё это ровно то, что делает официальный скрипт, только без похода на недоступный хост. Официальная ручная инструкция кладёт файлы в /usr, а скрипт выбирает первый подходящий каталог из /usr/local/bin, /usr/bin, /bin — по сути разницы нет.

Оговорка, чтобы не приписывать себе лишнего: контрольную сумму архива мы сверяли руками при скачивании, шага с проверкой SHA256 в самой роли нет. Автоматической частью повторяемого развёртывания это пока не стало — правильнее добавить сверку контрольной суммы прямо в задачу доставки файла, и у нас это в списке доработок.

Побочный вывод, который шире Ollama: если вы разворачиваете что-то в контуре с ограниченным выходом наружу, считайте установщик-однострочник недоступным по умолчанию. Он тянет файлы с хостов, которых нет в вашей документации и в вашем списке исключений, и падает не там, где сломался.

Ещё одна мелочь для тех, кто ставит через Ansible: доставку архива на 1,3 ГБ мы делаем модулем copy, а не synchronize. synchronize ломается, если порт SSH задан шаблоном — параметр dest_port не приводится к числу. Заливка разовая, дальше срабатывает проверка на наличие бинарника, так что скорость copy тут некритична.

Грабля 2. У Ollama нет аутентификации — и API key от локального сервера не спасает

Это, пожалуй, главное, что стоит унести из статьи, если больше ничего не уносить.

Ollama слушает порт 11434. По умолчанию — только на локальном интерфейсе, и в этом виде она безопасна ровно потому, что снаружи недоступна. Как только вам понадобилось обратиться к ней с другой машины — а на сервере это ровно тот случай, ради которого сервер и заводили, — вы меняете адрес привязки на 0.0.0.0 и получаете открытый API без пароля, токена и любой другой проверки, кто вы такой.

Тут уместно развеять популярное заблуждение. Запрос «ollama api key» ищут регулярно, и ответ на него не тот, который ожидают. Ключ у Ollama есть — но он относится к облаку ollama.com: переменная OLLAMA_API_KEY, ключ заводится в личном кабинете, и нужен он для облачных моделей, публикации моделей и доступа к приватным. К вашему локальному серверу этот ключ отношения не имеет. Локальная Ollama не проверяет вообще ничего: ни ключей, ни пользователей, ни ролей. Клиентские библиотеки OpenAI требуют заполнить поле с ключом — туда пишут любую строку, локальный сервер её игнорирует. Если у вас в конфиге стоит ключ и всё работает, это не значит, что доступ защищён; это значит, что поле просто не проверяется.

Что получает человек, дотянувшийся до вашего порта 11434: возможность гонять запросы на вашем процессоре, скачивать и удалять ваши модели, читать список того, что у вас развёрнуто. Плюс, если вы подаёте туда рабочие тексты, — канал, по которому чужой запрос попадёт в ту же модель.

Как это закрыто у нас. Привязка 0.0.0.0:11434, потому что к модели ходит соседняя машина с ботом. Машина и так живёт в изолированном сегменте — публичных адресов нет, проброса портов наружу нет, — но на одну топологию сети мы не полагаемся: на самом порту стоит правило межсетевого экрана (nftables), которое пропускает tcp/11434 только с адреса бота, а всё остальное отбрасывает. Loopback не трогаем — ollama pull ходит через 127.0.0.1. Правило раскатывается той же Ansible-ролью, что и сама Ollama, и проверено на живой машине, чтобы «вроде закрыто» стало «закрыто и подтверждено».

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

Чего мы не делали и потому не советую с моих слов: обратный прокси с basic-аутентификацией или mTLS перед Ollama мы не поднимали. Если вам нужен доступ из недоверенной сети, это правильный путь, но описывать его как «у нас так» я не буду.

Грабля 3. Модель выгружается из памяти — OLLAMA_KEEP_ALIVE

По умолчанию Ollama держит модель в памяти пять минут после последнего запроса, а потом выгружает. Логика понятная: на десктопе память нужна не только ей.

На сервере, где машина заведена под одну-единственную модель, это чистый вред: любой запрос после паузы дополнительно платит за загрузку пяти гигабайт весов, и со стороны это выглядит как непредсказуемое время ответа.

Лечится одной переменной окружения в юните: OLLAMA_KEEP_ALIVE=-1 — отрицательное значение велит держать модель загруженной постоянно. Мы поставили её сразу, при первом развёртывании, поэтому на эти грабли не наступали. Пишу о них именно потому, что ставить эту переменную имеет смысл сразу: поймать плавающую задержку в проде и потом искать её причину — дороже, чем одна строка в юните.

Грабля 4. Таймаут клиента, унаследованный от облака — подключение по OpenAI-совместимому API

Сначала про само подключение, потому что спрашивают об этом чаще всего. Ollama отдаёт OpenAI-совместимый эндпоинт: путь /v1 на порту 11434. Для приложения, которое уже умеет ходить в облачную модель, переключение — это по сути три значения в конфиге: базовый адрес (вместо адреса облачного провайдера — адрес вашей машины с Ollama и путём /v1), имя модели (у нас qwen2.5:7b) и поле ключа, куда пишется любая непустая строка. Библиотеку менять не нужно — тот же клиент OpenAI работает без правок кода.

А вот четвёртое значение переносить нельзя, и это грабля. Наш бот изначально ходил в облачную модель, и в конфиге у него стоял таймаут в 20 секунд — для облака с запасом. Локальная модель на процессоре генерирует ответ заметно медленнее облачной, и длинный ответ в такой таймаут не укладывается.

Оговорюсь честно: мы поймали это на разборе конфига, а не на инциденте — оборванных ответов в проде не наблюдали. Именно поэтому и записали пункт в карточку сервиса как обязательный при переключении на локального провайдера.

Вывод простой и переносимый: при переключении приложения с облачной модели на локальную таймаут клиента надо поднимать, и это отдельная задача, а не деталь конфига.

Грабля 5. Сервис включён, а машина не стартует

Ollama у нас поднимается штатно: systemctl enable, автозапуск, перезапуск при падении.

Только это не помогает, если после перезагрузки гипервизора сама виртуальная машина не стартует. У нас у части гостей не был выставлен флаг автозапуска — и всплыло это не при разборе Ollama, а когда мы собирали инвентарь виртуальных машин под аппаратную работу на узле. Заодно это объяснило, почему после аварийных перезагрузок (узел уходил в ребут по ошибкам памяти) бот оказывался мёртвым. Флаг автозапуска проставили — и машине с ботом, и машине с Ollama.

Грабля не про Ollama, а про то, как её обычно разворачивают: под неё заводят отдельную машину, потому что модель не влезает в память к остальному, — и эта новая машина легко выпадает из списка того, что проверяется после перезагрузки. Проверьте у себя две вещи, а не одну: автозапуск сервиса и автозапуск гостя.

Как обновлять Ollama, когда установщик недоступен

Из грабли 1 следует вывод, который редко проговаривают: если релизный CDN из вашей сети не работает, то недоступен не только первый запуск, но и каждое обновление. Однострочник curl … | sh, который в обычной жизни и ставит, и обновляет, у вас не сделает ни того, ни другого.

У нас процедура обновления такая же, как установка, и это сознательно: скачать архив новой версии на управляющую машину, сверить контрольную сумму, доставить на сервер, распаковать поверх, перезапустить сервис. Роль идемпотентна по наличию бинарника, поэтому для обновления версия задаётся явно.

Практический смысл в том, что обновление в закрытом контуре — это ручной шаг, который надо запланировать, а не фоновая операция. Если вы строите контур без выхода наружу, закладывайте это в регламент сразу: кто следит за выходом релизов, кто приносит архив внутрь, как проверяется его целостность. Иначе через год у вас будет работающий сервис на версии, о происхождении которой никто не помнит.

Какую модель мы поставили и почему qwen2.5:7b

Оговорюсь сразу: это не рейтинг моделей и не сравнение качества. Сравнения мы не проводили, и раздел «чего мы не делали» ниже это подтверждает. Здесь — только логика выбора под наши ограничения, её можно повторить у себя с другим итогом.

Ограничений было четыре:

  • Нет видеокарты. Значит, модель должна быть такого размера, чтобы приемлемо считаться на процессоре. Это сразу отсекает всё крупное.
  • 16 ГБ памяти на машину. Модель на 7B в квантизации Q4 занимает около 5 ГБ и оставляет запас на контекст и систему — см. расчёт ниже.
  • Русскоязычный бот. Модель должна нормально работать с русским, а не быть англоцентричной.
  • Модель должна скачаться. В нашей сети реестр registry.ollama.ai доступен, а релизный CDN GitHub — нет. Модель из штатного реестра Ollama — это в нашем контуре преимущество.

qwen2.5:7b в Q4 попадает во все четыре ограничения. Она обслуживает русскоязычного бота в работе и с задачей справляется — но это эксплуатационное впечатление, а не замер, и выдавать его за оценку качества я не буду.

Что здесь важнее конкретного имени модели: выбирают не «лучшую модель», а самую крупную из тех, что укладывается в вашу память с запасом на контекст. Размер модели упирается в железо раньше, чем в качество, и в этом порядке решения и надо принимать.

Почему мы запустили без видеокарты и когда GPU обязателен

Вопрос «нужна ли видеокарта» — самый частый и самый плохо отвечаемый. Отвечу по своей развилке, без чужих замеров.

Нам хватило процессора, потому что совпали три условия:

  • Потребитель один. Запросы приходят по очереди, параллельной нагрузки нет. Основное, что даёт GPU, — это пропускная способность на потоке запросов, а её нам некуда девать.
  • Ответ ждёт не человек в чате «прямо сейчас», а бот в фоне. Секунды задержки для этого сценария допустимы.
  • Модель небольшая. 7B в Q4 на восьми ядрах считается, инференс на процессоре хорошо утилизирует потоки.

Видеокарта становится обязательной, когда любое из трёх ломается: пользователей несколько и они приходят одновременно; ответ нужен в интерактивном темпе, когда человек смотрит на экран; или модель заметно крупнее, чем 7B, — тогда на процессоре она из «медленно» переходит в «неприемлемо».

Практический вывод: сначала честно опишите профиль нагрузки, потом покупайте железо. «Локальная LLM обязательно требует видеокарту» — неверно как утверждение общего вида; неверно и обратное. Требует её конкретный сценарий, а не сама идея self-host. Наш случай — один потребитель, редкие запросы, требование «данные не уходят наружу» — закрывается процессором, и мы на это пошли сознательно, а не от бедности.

Сколько нужно RAM и диска: системные требования Ollama на практике

Здесь чаще всего гадают, поэтому дам методику, а не ощущения.

Память под модель складывается из трёх слагаемых:

  • веса модели — для 7B в Q4 это примерно 5 ГБ;
  • кэш контекста (KV-кэш — то, что модель помнит в пределах текущего диалога) — ещё 1–2 ГБ, и он растёт вместе с длиной контекста;
  • операционная система и запас — иначе первый же длинный диалог упрётся в лимит.

Отсюда наши 16 ГБ на машину. Заметьте: официальный минимум для модели такого размера — 8 ГБ, и в них она формально влезает, но без запаса на контекст. На боевой машине я бы так не делал.

Почему отдельная машина: у бота своя, на 4 ГБ, и туда модель не помещается физически. Это типичная развилка — либо раздувать существующую машину, либо заводить отдельную. Мы выбрали отдельную, потому что у инференса другой профиль нагрузки и другая критичность.

Диск: 40 ГБ. Одна модель на 7B занимает около 5 ГБ, то есть места хватает на две-три. Модели лежат обычными файлами в домашнем каталоге пользователя, от которого запущен сервис. Если у вас корневой раздел маленький — это первое, что переполнится, и заметите вы это в момент pull новой модели.

Процессор: 8 vCPU. Инференс на процессоре хорошо утилизирует потоки, так что это не «на всякий случай», а осмысленный параметр.

Наша единственная измеренная цифра: около 2,4 секунды на ответ на этой конфигурации, на рабочих запросах бота. Оговорюсь честно — это не бенчмарк: мы не мерили токены в секунду, не прогоняли серию с фиксированной длиной запроса и не сравнивали квантизации между собой. Это эксплуатационное наблюдение на нашей нагрузке, и переносить его на свою конфигурацию как обещание нельзя. Цифр, которых мы не измеряли, в этой статье не будет.

Ollama vs vLLM, llama.cpp и LM Studio: что мы выбрали и почему

Короткий ответ: потому что у нас нет видеокарты.

vLLM — движок для серверного инференса, который умеет то, чего Ollama не умеет: делить одну модель между несколькими видеокартами так, чтобы они считали один запрос сообща, и держать поток параллельных запросов. Под многопользовательскую нагрузку на GPU-сервере брать надо его. Но он и рассчитан на GPU, которого у нас в этом узле нет.

Ollama в датацентре — это простой внутренний эндпоинт для одной команды или одного приложения. Ровно наш случай: один потребитель, редкие запросы, требование «данные не уходят наружу». Называть это production-инференсом было бы враньём, и мы его так не называем.

Про llama.cpp стоит сказать отдельно, потому что вопрос «Ollama или llama.cpp» задают как выбор между конкурентами, а это не совсем так: Ollama построена на llama.cpp — это базовый движок локального инференса, поверх которого Ollama добавляет реестр моделей, управление их загрузкой и выгрузкой, systemd-дружелюбный сервер и OpenAI-совместимый эндпоинт. Брать llama.cpp напрямую осмысленно, если вы хотите встроить инференс в своё приложение или тонко управлять параметрами сборки. Нам нужен был готовый сетевой эндпоинт с минимумом обвязки, поэтому мы взяли слой выше и ни разу об этом не пожалели.

LM Studio в этом сравнении не участвует: это десктопное приложение с окном, а у нас сервер без графической оболочки.

Зачем свой сервер, если есть Ollama Cloud

У Ollama есть облачная подписка: часть моделей помечена суффиксом :cloud и считается на серверах вендора. Это удобно и снимает вопрос с железом.

Для нас это не подходит по причине, которая не имеет отношения к деньгам. Смысл нашего контура в том, что данные не покидают периметр. Как только запрос уходит на сервер вендора в другой юрисдикции, весь смысл упражнения теряется — и, если в запросе есть персональные данные, это уже разговор не про удобство, а про 152-ФЗ и трансграничную передачу.

Отдельно про оплату, раз уж этот вопрос ищут чаще самой подписки. Ollama Cloud — зарубежный сервис с зарубежным приёмом платежей, и для российской компании это отдельная задача со своими издержками и своими рисками по непрерывности. Мы её не решали: вопрос снялся раньше, на уровне данных, — если запросы всё равно нельзя отправлять за периметр, платить не за что. Поэтому рассказать, как именно оплатить подписку из России, я не могу и выдумывать не буду. Скажу другое, и это как раз наш опыт интегратора: в закрытом контуре подписка на зарубежное облако — это ещё и точка отказа, не зависящая от вас. Свой сервер платится один раз железом и не отключается решением чужой стороны.

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

И ещё одна честная оговорка, раз уж речь про закрытый контур: локальная модель сама по себе не делает контур закрытым. У нашего бота модель локальная, но выход в Telegram идёт через прокси наружу — то есть канал, по которому данные покидают периметр, в системе есть, просто он в другом месте. Self-host — необходимое условие, а не достаточное.

Чего мы не делали

Раздел, которого обычно нет, а зря. Всё перечисленное ниже мы не разворачивали — значит, ничего полезного об этом сказать не можем:

  • Docker. У нас systemd-юнит на выделенной машине. Официальный образ существует, мы им не пользовались.
  • Open WebUI и любой другой веб-интерфейс. Единственный потребитель — приложение, человеку туда ходить не нужно.
  • Вызов функций (tools). Не проверяли, как локальная 7B-модель справляется с инструментами.
  • Обратный прокси с basic-аутентификацией или mTLS перед Ollama. Нам он не понадобился: порт закрыт межсетевым экраном на адрес бота, доступа из недоверенных сетей нет. Если он вам нужен — это правильный путь, но описывать его как «у нас так» мы не будем.
  • Сверка контрольной суммы внутри Ansible-роли. Сверяем руками, автоматизации нет.
  • RAG, поиск по документам, эмбеддинги. Векторной базы у нас нет — это наш следующий шаг, и статья про него появится, когда он будет сделан, а не раньше.
  • Работа на видеокарте. Ни NVIDIA, ни AMD в этом контуре нет, поэтому ничего про CUDA, ROCm и распределение по нескольким картам из личного опыта сказать не могу.
  • Формальная оценка качества на русском языке. Модель обслуживает русскоязычного бота в продуктиве и с задачей справляется, но мы не проводили сравнения моделей по качеству русского и не имеем права выдавать своё впечатление за замер.

Выводы, применимые у вас

  1. Установщик-однострочник — не гарантия. В контуре с ограниченным выходом наружу заранее выясните, с каких хостов он тянет файлы. Ошибка 404 при установке Ollama может означать блокировку совсем другого хоста.
  2. Порт 11434 без пароля. Если вы сменили адрес привязки на 0.0.0.0, вы открыли API без аутентификации. Закройте порт правилом межсетевого экрана на адрес клиента (у нас это nftables, пропускающий tcp/11434 только с адреса бота) — и не полагайтесь на одну сегментацию сети. Ключ OLLAMA_API_KEY относится к облаку ollama.com и ваш локальный сервер не защищает.
  3. OLLAMA_KEEP_ALIVE=-1 на выделенном сервере. Иначе запрос после паузы платит за загрузку модели. Ставьте сразу — ловить плавающую задержку потом дороже.
  4. Поднимите таймаут клиента, переключая приложение с облачной модели на локальную. Всё остальное в конфиге переносится один в один, а таймаут — нет.
  5. Считайте память как «веса + контекст + система», а не по минимальным требованиям. Для 7B в Q4 практический ориентир — 16 ГБ, а не 8.
  6. Проверьте автозапуск и сервиса, и виртуальной машины. Новая машина под инференс легко выпадает из списка того, что поднимается после перезагрузки узла.
  7. В закрытом контуре обновление — ручная процедура. Заложите в регламент, кто и как приносит новую версию внутрь: установщик-однострочник её вам не принесёт.
STABOS — системный интегратор

Строим и обслуживаем инфраструктуру малого и среднего бизнеса

Тема этой статьи — часть услуги «Внедрение ИИ и закрытые контуры». Начните с аудита: покажем реальное состояние и риски, дадим план.