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):
- Архив с бинарником лежит не на сайте Ollama, а на релизном CDN GitHub —
release-assets.githubusercontent.com. Из нашей сети этот хост не отдаёт тело ответа: соединение устанавливается, а данные не идут — 0 байт за 40 секунд, потом таймаут. - Формат архива у 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 и распределение по нескольким картам из личного опыта сказать не могу.
- Формальная оценка качества на русском языке. Модель обслуживает русскоязычного бота в продуктиве и с задачей справляется, но мы не проводили сравнения моделей по качеству русского и не имеем права выдавать своё впечатление за замер.
Выводы, применимые у вас
- Установщик-однострочник — не гарантия. В контуре с ограниченным выходом наружу заранее выясните, с каких хостов он тянет файлы. Ошибка 404 при установке Ollama может означать блокировку совсем другого хоста.
- Порт 11434 без пароля. Если вы сменили адрес привязки на
0.0.0.0, вы открыли API без аутентификации. Закройте порт правилом межсетевого экрана на адрес клиента (у нас это nftables, пропускающийtcp/11434только с адреса бота) — и не полагайтесь на одну сегментацию сети. КлючOLLAMA_API_KEYотносится к облакуollama.comи ваш локальный сервер не защищает. OLLAMA_KEEP_ALIVE=-1на выделенном сервере. Иначе запрос после паузы платит за загрузку модели. Ставьте сразу — ловить плавающую задержку потом дороже.- Поднимите таймаут клиента, переключая приложение с облачной модели на локальную. Всё остальное в конфиге переносится один в один, а таймаут — нет.
- Считайте память как «веса + контекст + система», а не по минимальным требованиям. Для 7B в Q4 практический ориентир — 16 ГБ, а не 8.
- Проверьте автозапуск и сервиса, и виртуальной машины. Новая машина под инференс легко выпадает из списка того, что поднимается после перезагрузки узла.
- В закрытом контуре обновление — ручная процедура. Заложите в регламент, кто и как приносит новую версию внутрь: установщик-однострочник её вам не принесёт.
Строим и обслуживаем инфраструктуру малого и среднего бизнеса
Тема этой статьи — часть услуги «Внедрение ИИ и закрытые контуры». Начните с аудита: покажем реальное состояние и риски, дадим план.