Как защитить API Ollama паролем
В Ollama отсутствует встроенная аутентификация, поэтому любой доступ к порту 11434 позволяет управлять моделями. В статье разобраны три способа настройки защиты вашего сервера.
В API Ollama отсутствует пароль
В API Ollama нет аутентификации. На сервере, который вы запускаете, нет пользователей, паролей, проверки ключей или списков разрешенных IP-адресов. Любой, кто может установить TCP-соединение с портом 11434, может просматривать список ваших моделей, запускать их, загружать новые и удалять существующие.
В официальной документации это прямо указано: "При локальном доступе к API Ollama через http://localhost:11434 аутентификация не требуется". Слово локально определяет всю модель безопасности. По умолчанию Ollama привязывается к 127.0.0.1, поэтому на ноутбуке интерфейс обратной петли (loopback) выступает в роли контроля доступа. Если перенести прослушивание на публичный адрес, контроль доступа исчезнет, так как ничто не пришло ему на смену.
Именно поэтому данный вопрос критичен для VPS (виртуального выделенного сервера). Настройки по умолчанию безопасны. Первое изменение, которое вносит большинство пользователей — открытие доступа для прослушивания, чтобы вторая машина могла использовать модель — одновременно отключает всю защиту.
Что раскрывает открытый порт 11434
Каждая конечная точка. Здесь нет режима «только для чтения» и отдельного порта администратора. Это реальные запросы, направленные на адрес сервера, а не на localhost:
# List every model on the box
curl http://SERVER_IP:11434/api/tags
# See what is loaded into memory right now
curl http://SERVER_IP:11434/api/ps
# Run a prompt on your hardware
curl http://SERVER_IP:11434/api/generate -d '{"model":"llama3.2","prompt":"Why is the sky blue?"}'
# Write several gigabytes to your disk
curl http://SERVER_IP:11434/api/pull -d '{"model":"llama3.2"}'
# Remove a model
curl -X DELETE http://SERVER_IP:11434/api/delete -d '{"model":"llama3.2"}'С точки зрения оператора, возникают четыре проблемы:
- Ваш CPU или GPU выполняет инференс для кого-то другого. На тарифах с лимитом использования CPU постоянная нагрузка означает, что ваш лимит расходует посторонний человек, а контроль расходов на ИИ-нагрузки на VPS становится гораздо сложнее, когда вы не единственный клиент.
/api/pullзаписывает данные на ваш диск. Модели весят от 2 до 40 гигабайт каждая. Цикл загрузок заполняет том, а переполненный диск выводит из строя все остальные сервисы на машине, а не только Ollama.- Запросы поступают в ваш процесс и регистрируются в логах. При стандартном уровне логирования Ollama записывает только метаданные: конечную точку, статус, задержку и адрес клиента, но не текст промпта. Тем не менее, это запись о том, кто использовал вашу машину и для чего, которая хранится в вашем журнале, хотя вы не планировали её собирать.
/api/deleteудаляет модели. Чтобы вернуть их, потребуется повторная загрузка, расходующая ваш трафик.
Для всего этого не нужны эксплойты. Это задокументированный API, работающий в точном соответствии с проектом.
Ключ Ed25519 не является средством контроля доступа
Поиск по запросу "Ollama API key" приводит к двум разным понятиям. Ни одно из них не является паролем для вашего сервера, и их разделение устраняет большую часть путаницы.
Первое — это пара ключей идентификации. Ollama генерирует пару ключей Ed25519 при первом запуске. В Linux скрипт установки создает системного пользователя с именем ollama, чей домашний каталог находится в /usr/share/ollama, поэтому пара ключей хранится здесь:
/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pubЭтот ключ направлен вовне. ollama signin регистрирует открытую часть ключа в вашей учетной записи ollama.com, и именно она авторизует вас для отправки модели в реестр или получения приватной модели. Она подтверждает ollama.com, что запрос исходит с вашей машины. Она никак не ограничивает клиентов, подключающихся к вашему серверу. Удаление, ротация или отсутствие этого ключа никак не влияют на то, кто может обращаться к вашему API.
Второе — это OLLAMA_API_KEY. Эта переменная содержит ключ, который вы создаете на https://ollama.com/settings/keys, и ваш клиент отправляет его в заголовке Authorization: Bearer $OLLAMA_API_KEY при обращении к размещенному API по адресу https://ollama.com/api. Это учетные данные для их сервиса, которые используете вы как клиент. Ваш собственный ollama serve никогда не считывает этот ключ. Установка OLLAMA_API_KEY на вашем VPS не устанавливает пароль на сам VPS.
Таким образом, не существует настройки, которую можно просто включить. Три описанных ниже метода защиты работают одинаково: ограничьте доступ к порту и установите перед ним прокси-сервер, который будет выполнять проверку.
Проверка портов, которые прослушивает сервер в данный момент
sudo ss -tlnp | grep 11434Безопасный результат указывает на loopback-адрес:
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))Результат с открытым доступом указывает на все сетевые интерфейсы:
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))0.0.0.0 означает все IPv4-адреса на сервере, включая публичный. *:11434 и [::]:11434 означают то же самое с учетом IPv6.
Теперь подтвердите доступность извне. Выполните эту команду на своем ноутбуке, а не на сервере:
curl -m 5 http://YOUR_SERVER_IP:11434/api/versioncurl: (28) Connection timed out after 5001 milliseconds — это ожидаемый ответ, как и curl: (7) Failed to connect ... Connection refused. JSON-объект, содержащий поле version, означает, что API доступен любому желающему. Тестирование через curl на самом сервере ничего не доказывает, так как loopback отвечает всегда.
Открытие доступа обычно происходит одним из двух способов. Первый — это намеренное редактирование конфигурации, когда потребовалось подключиться к модели с другого компьютера:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"Эта единственная строка и есть причина открытия доступа. Второй способ — Docker, который не требует редактирования каких-либо файлов. Для этого случая ниже есть отдельный раздел.
Защита 1: привязка к localhost и использование туннеля
Используйте этот метод в первую очередь. Он не требует установки дополнительного ПО и не создает учетных данных, которые могут быть скомпрометированы. Порт не открывается на публичном интерфейсе, поэтому сканирование не обнаружит сервис.
Явно укажите адрес привязки вместо использования значения по умолчанию:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"Эта команда создает файл /etc/systemd/system/ollama.service.d/override.conf. Примените изменения и проверьте результат:
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434ss теперь должен отображать 127.0.0.1:11434. Если по-прежнему выводится 0.0.0.0, значит, приоритет имеет другой файл конфигурации (drop-in). Выполните systemctl cat ollama.service, чтобы вывести список юнита и всех связанных с ним файлов с указанием путей, после чего удалите устаревший файл.
Для доступа к модели с вашего ноутбука пробросьте порт через SSH:
ssh -N -L 11434:127.0.0.1:11434 you@your-server-L 11434:127.0.0.1:11434 открывает порт 11434 на вашем ноутбуке и перенаправляет весь трафик на 127.0.0.1:11434 с точки зрения сервера. -N указывает SSH не выполнять удаленную команду, поэтому процесс просто поддерживает туннель открытым. Пока он запущен, на вашем ноутбуке будет работать следующая команда:
curl -s http://localhost:11434/api/tagsВы можете столкнуться с двумя ошибками. bind [127.0.0.1]:11434: Address already in use означает, что на вашем ноутбуке уже запущен собственный экземпляр Ollama на этом порту; выберите другой локальный порт с помощью -L 11500:127.0.0.1:11434 и направьте клиент на 11500. Пустой ответ через успешно установленный туннель означает, что SSH работает, но Ollama не прослушивает порт на стороне сервера; проверьте ss на сервере, прежде чем менять команду SSH.
Если клиентов несколько, частная сеть лучше, чем отдельный туннель для каждого пользователя. Объедините машины в сеть через WireGuard или Tailscale, а затем привяжите Ollama к адресу в этой сети вместо 0.0.0.0:
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"В этом случае порт будет существовать только на интерфейсе, для доступа к которому требуется ключ. Это защищает от ошибок в настройках брандмауэра: даже если правило случайно откроет доступ для всех, сервис останется недоступным извне, так как он не привязан к публичному интерфейсу.
Защита 2: обратный прокси с проверкой bearer-токена
Если к модели требуется доступ из публичной сети, оставьте Ollama на loopback-интерфейсе и установите перед ней прокси. Прокси выполняет TLS termination и отклоняет запросы без корректного заголовка. Ollama по-прежнему принимает соединения только с 127.0.0.1, поэтому прокси остается единственным путем доступа.
Сначала создайте настоящий токен. Не придумывайте его вручную:
openssl rand -base64 36Конфигурация сайта nginx для проверки токена:
map $http_authorization $ollama_ok {
default 0;
"Bearer PASTE_YOUR_GENERATED_TOKEN_HERE" 1;
}
server {
listen 443 ssl;
server_name llm.example.com;
ssl_certificate /etc/letsencrypt/live/llm.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/llm.example.com/privkey.pem;
location = /api/pull { return 403; }
location = /api/delete { return 403; }
location = /api/push { return 403; }
location / {
if ($ollama_ok = 0) { return 401; }
proxy_pass http://127.0.0.1:11434;
proxy_set_header Host 127.0.0.1:11434;
proxy_buffering off;
proxy_read_timeout 600s;
}
}Пять строк здесь выполняют основную работу, каждая из них предотвращает сбой, с которым вы иначе столкнетесь.
Использование if внутри блока location в nginx обычно считается плохой практикой, но тело размером ровно return — это одна из двух форм, работающих предсказуемо, поэтому такой вариант безопасен.
location = /api/pull — это точное совпадение, и nginx ставит точные совпадения выше префикса location /, поэтому доступ к этим трем эндпоинтам будет запрещен еще до проверки токена. Валидный токен дает право на инференс, а не на заполнение диска.
proxy_set_header Host 127.0.0.1:11434; важен, так как Ollama проверяет входящие заголовки Host и Origin. Передача публичного имени хоста прокси напрямую может привести к тому, что 403 Forbidden придет от Ollama, а не от nginx, что затрудняет отладку. OLLAMA_ORIGINS — это еще один рычаг для браузерного клиента, которому требуется разрешение конкретного origin.
proxy_buffering off; важен, так как Ollama передает ответ потоком, токен за токеном. При включенном буферизации nginx удерживает поток и отдает его целиком только в конце, из-за чего клиент выглядит зависшим на все время генерации.
proxy_read_timeout 600s; важен, так как по умолчанию в nginx установлено 60 секунд. Длительная генерация на CPU легко превышает этот лимит, клиент получает 504 Gateway Time-out, а в /var/log/nginx/error.log записывается upstream timed out (110: Connection timed out) while reading response header from upstream. Запрос продолжал выполняться, но nginx прервал его.
Перезагрузите конфигурацию и протестируйте оба пути:
sudo nginx -t && sudo systemctl reload nginx
curl -s -o /dev/null -w '%{http_code}\n' https://llm.example.com/api/tags
curl -s -H "Authorization: Bearer YOUR_TOKEN" https://llm.example.com/api/tagsПервый должен вывести 401. Второй должен вывести список ваших моделей. Если первый запрос также возвращает список моделей, значит, блок map находится не в той области видимости. Он должен располагаться на уровне http, поэтому поместите его в файл в директории /etc/nginx/conf.d/ или перед блоком server, но никогда не внутри server.
Caddy выполняет ту же задачу с помощью базовой аутентификации всего в четырех строках, что лучше подходит для браузерного клиента, чем bearer-токен:
llm.example.com {
basic_auth {
apiuser PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:11434
}Запустите caddy hash-password, чтобы получить bcrypt-хеш, который ожидает Caddy. Важный нюанс с именованием: директива называлась basicauth до версии Caddy v2.8, а теперь называется basic_auth, поэтому конфигурация, скопированная из старого руководства, не загрузится, и Caddy укажет на нераспознанную директиву.
Какой бы прокси вы ни выбрали, это будет один общий секрет для всех. Каждый клиент, обладающий им, имеет идентичный доступ, а отзыв токена означает необходимость редактирования конфигурации и одновременного обновления всех клиентов.
Защита 3: шлюз, выдающий ключи для каждого клиента
Когда модель используют более одного человека или приложения, общий токен быстро исчерпывает лимиты. Вы не можете определить, какой именно клиент создает нагрузку, и не можете ограничить одного из них, не отключив остальных. Шлюз устанавливается на место прокси, поддерживает тот же API, совместимый с OpenAI, выдает отдельный ключ для каждого клиента и ведет учет использования для каждого ключа. Самостоятельно развернутый шлюз LiteLLM — стандартное решение, которое добавляет управление бюджетами для каждого ключа и логирование запросов поверх контроля доступа.
Правило из защиты 1 остается неизменным. Ollama привязывается к 127.0.0.1, шлюз является единственным процессом, который взаимодействует с ней, и шлюз — единственный сервис с публичным портом. Шлюз на сервере, где порт 11434 все еще открыт для внешнего мира, является лишь декорацией, так как клиенты могут просто обойти его.
Ловушка брандмауэра: опубликованный порт контейнера обходит UFW
Именно поэтому открытые экземпляры сервисов существуют на серверах, владельцы которых правильно настроили брандмауэр.
UFW (uncomplicated firewall) записывает свои правила в цепочку INPUT таблицы filter ядра, а INPUT обрабатывает пакеты, адресованные самому хосту. Флаг -p в Docker добавляет правило трансляции сетевых адресов (destination NAT) в цепочку PREROUTING таблицы nat, которое ядро проверяет до принятия решения о маршрутизации пакета. К моменту принятия решения о маршрутизации адрес назначения уже изменен на адрес контейнера, поэтому пакет пересылается, а не доставляется локально, и проходит через FORWARD вместо INPUT. Правила INPUT в UFW никогда не задействуются, поэтому пакет проходит мимо брандмауэра, а не через него.
Вот почему эта последовательность оставляет порт 11434 открытым для интернета:
sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollamaа sudo ufw status по-прежнему сообщает, что брандмауэр активен с политикой запрета по умолчанию. Оба показания верны одновременно, и именно поэтому пользователи доверяют неверному из них. Вы можете увидеть правило, которое это сделало:
sudo iptables -t nat -L DOCKER -nРешение заключается в указании адреса во флаге публикации:
docker rm -f ollama
docker run -d -v ollama:/root/.ollama -p 127.0.0.1:11434:11434 --name ollama ollama/ollama-p 11434:11434 — это сокращение для -p 0.0.0.0:11434:11434. Указание 127.0.0.1 привязывает хостовую сторону маппинга к интерфейсу обратной петли (loopback), поэтому ваш SSH-туннель и обратный прокси по-прежнему смогут получить к нему доступ, а интернет — нет. Пересоздание контейнера здесь безопасно, так как модели хранятся в именованном томе ollama, а не внутри контейнера.
Убедитесь, что оба представления согласуются:
docker port ollama
sudo ss -tlnp | grep 11434docker port ollama должен вывести 11434/tcp -> 127.0.0.1:11434. Если он выводит 0.0.0.0:11434, вы по-прежнему уязвимы. Изучите этот механизм один раз, и он будет применим к любому контейнеру, который вы публикуете: почему опубликованные порты Docker обходят UFW описывает цепочку DOCKER-USER и правила, которые сохраняются после перезапуска Docker. Если вы все еще настраиваете политику самого хоста, правила UFW, необходимые для нового VPS описывают базу, на которой строится эта конфигурация.
От имени какого пользователя запущен процесс
Скрипт установки в Linux создает выделенную учетную запись и запускает службу от ее имени:
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaЮнит в /etc/systemd/system/ollama.service затем устанавливает User=ollama и Group=ollama. Не меняйте эти параметры. Быстрый запуск ollama serve вручную в терминале выполняется от имени пользователя, под которым вы вошли в систему. Если это root, то неаутентифицированный API будет записывать файлы с правами root. Проверьте, какой пользователь используется:
ps -o user= -C ollamaРезультатом должен быть ollama. Любое другое значение означает, что процесс, запущенный вручную, работает параллельно с юнитом или вместо него. Тот же подход применим к любому демону, который вы добавите позже, и запуск служб от имени пользователей с минимальными привилегиями подробно описывает этот процесс.
Как проверить безопасность API endpoint в Ollama
Что бы вы ни выбрали, один тест даст окончательный ответ. Его необходимо запустить с другой машины:
curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tagsВ обоих случаях соединение должно завершиться по таймауту или быть отклонено. Если вы настроили прокси, те же два пути на хостнейме прокси должны возвращать 401 без учетных данных и корректный JSON при их наличии.
Затем один раз просмотрите access log, так как он покажет, обнаружил ли кто-либо порт, пока он был открыт:
journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1Ollama записывает по одной строке на каждый запрос, включая адрес клиента:
[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"Каждая строка должна содержать 127.0.0.1, как только Ollama будет привязана к loopback, поскольку это единственный адрес, с которого может поступить соединение. Публичный адрес в этом столбце означает запрос извне, а временная метка укажет, когда это произошло. Отсутствие вывода по этой команде — желаемый результат. Если работа с моделями для вас в новинку, в запуске Ollama на VPS описаны установка, подбор размера моделей и лимиты памяти, от которых зависит, что именно будет загружено.
FAQ
Есть ли у Ollama API-ключ или пароль?
Нет. Сервер, который вы запускаете, не имеет никакой аутентификации, и официальная документация гласит, что для доступа к API она не требуется. Оба понятия, называемые «Ollama API key», означают обратное. Пара Ed25519 в /usr/share/ollama/.ollama/ подтверждает вашу машину для ollama.com, чтобы вы могли загружать модели и скачивать приватные. OLLAMA_API_KEY — это учетные данные, которые ваш клиент отправляет на хостинговый API по адресу https://ollama.com/api. Ваш собственный ollama serve не считывает ни то, ни другое, поэтому контроль доступа должен обеспечиваться сетью или прокси-сервером перед ним.
Безопасен ли OLLAMA_HOST=0.0.0.0, если у меня есть межсетевой экран?
Только до тех пор, пока никто другой не вносит правила в межсетевой экран на этом узле. 0.0.0.0 означает, что слушатель действительно существует на публичном интерфейсе, и вы полагаетесь только на межсетевой экран, чтобы сделать его недоступным. Это доверие пропадает в момент, когда Docker публикует порт, так как правило DNAT, которое Docker добавляет в таблицу nat, оценивается до того, как пакет достигнет цепочки INPUT, где работает UFW. В результате пакет пересылается, а UFW его не видит. Привязка к 127.0.0.1 или к адресу частного туннеля удаляет слушатель с публичного интерфейса, поэтому ошибка в настройках межсетевого экрана не приведет к раскрытию сервиса.
Как проверить, открыт ли мой порт Ollama для интернета?
Запустите sudo ss -tlnp | grep 11434 на сервере и curl -m 5 http://YOUR_SERVER_IP:11434/api/version с другой машины. ss, показывающий 127.0.0.1:11434, при том что удаленный curl выдает таймаут — это тот результат, который вам нужен. ss, показывающий 0.0.0.0:11434 или *:11434, в то время как удаленный curl возвращает JSON, означает, что API полностью доступен. Никогда не тестируйте с помощью curl на самом сервере, так как loopback отвечает независимо от того, какой адрес привязки установлен.
Могу ли я просто сменить порт с 11434 на какой-нибудь случайный?
Нет, и причина заслуживает упоминания. Другой порт не замедляет ничего, кроме сканирования одного конкретного порта. Сканеры проходят весь диапазон, и один запрос к /api/tags идентифицирует сервис независимо от того, на какой порт он пришел. Смена порта также нарушает настройки по умолчанию у всех клиентов и усложняет понимание вашей конфигурации в будущем. Вместо этого привяжитесь к loopback, что удалит слушатель, а не просто переместит его.
Кто-то получил доступ к моему открытому Ollama. Что мне проверить?
Сначала привяжите его к 127.0.0.1 и перезапустите сервис, чтобы прекратить доступ перед началом расследования. Затем запустите journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1, чтобы увидеть, какие внешние адреса обращались к каким эндпоинтам и когда. Сравните ollama list с моделями, которые вы планировали использовать, так как /api/pull не требует аутентификации, и модель, которую вы не скачивали, — это и расход дискового пространства, и улика. Проверьте свободное место с помощью df -h. Ollama не записывает текст запросов при стандартном уровне логирования, поэтому у вас есть запись о том, кто обращался и к какой модели, но не о том, что было сгенерировано.