Ollama API không có password: 3 cách bảo vệ port 11434
Ollama không có authentication mặc định: ai truy cập được port 11434 có thể chạy, tải và xóa model. Xem 3 cách xử lý theo thứ tự ưu tiên.
API của Ollama không có password
API của Ollama không có cơ chế xác thực. Server bạn chạy không có user, password, kiểm tra key hay allowlist nào. Bất kỳ ai có thể mở kết nối TCP đến port 11434 đều có thể liệt kê các model, chạy model, download model mới và xóa các model hiện có.
Tài liệu chính thức nêu rõ: "Không cần xác thực khi truy cập API của Ollama cục bộ qua http://localhost:11434." Từ cục bộ quyết định toàn bộ mô hình bảo mật. Theo mặc định, Ollama bind vào 127.0.0.1, nên trên laptop, interface loopback chính là cơ chế kiểm soát truy cập. Chuyển listener sang một địa chỉ public sẽ loại bỏ cơ chế kiểm soát truy cập này, vì không có cơ chế nào thay thế nó.
Đó là lý do vấn đề này quan trọng trên VPS (virtual private server). Cấu hình mặc định an toàn. Thay đổi đầu tiên mà nhiều người thực hiện là mở listener để máy thứ hai có thể sử dụng model. Chính thay đổi này đồng thời loại bỏ mọi lớp bảo vệ.
Một cổng 11434 đang mở làm lộ điều gì
Mọi endpoint. Không có chế độ chỉ đọc và cũng không có cổng admin riêng. Đây là các request thực tế, được gửi đến địa chỉ server thay vì 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"}'Ở góc độ vận hành, có 4 vấn đề xảy ra:
- CPU hoặc GPU của bạn chạy inference cho người khác. Với gói có hạn mức CPU theo chính sách fair-use, tải kéo dài sẽ tiêu tốn hạn mức của bạn do người lạ sử dụng. Kiểm soát chi phí workload AI trên VPS sẽ khó hơn nhiều khi bạn không còn là caller duy nhất.
/api/pullghi dữ liệu vào disk của bạn. Mỗi model có dung lượng từ 2 đến 40 gigabyte. Một vòng lặp pull sẽ làm đầy volume. Disk đầy sẽ làm hỏng mọi service khác trên máy, không chỉ Ollama.- Request đi vào process của bạn và được ghi log. Ở log level mặc định, Ollama chỉ ghi metadata, gồm endpoint, status, latency và địa chỉ client, không ghi nội dung prompt. Tuy vậy, log vẫn cho biết ai đã dùng máy của bạn và dùng vào việc gì. Thông tin này nằm trong journal dù bạn không chủ động thu thập.
/api/deletexóa model. Muốn có lại, bạn phải tải chúng xuống lần nữa bằng bandwidth của chính mình.
Không điều nào trong số này cần đến việc khai thác lỗ hổng. Đây là API được tài liệu hóa và đang hoạt động đúng theo thiết kế.
Khóa Ed25519 không phải là cơ chế kiểm soát truy cập
Tìm kiếm “Ollama API key” sẽ cho ra hai thứ khác nhau. Không thứ nào là mật khẩu của server. Phân biệt chúng sẽ loại bỏ phần lớn nhầm lẫn.
Thứ nhất là cặp khóa định danh. Ollama tạo một cặp khóa Ed25519 trong lần chạy đầu tiên. Trên Linux, install script tạo một system user có tên ollama, với home directory tại /usr/share/ollama. Vì vậy, cặp khóa nằm ở đây:
/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pubKhóa này dùng cho kết nối đi ra ngoài. ollama signin đăng ký phần public key với tài khoản ollama.com của bạn. Đây là khóa cho phép bạn push model lên registry hoặc pull model private. Nó xác thực máy của bạn với ollama.com. Nó không yêu cầu gì từ các client kết nối đến máy của bạn. Xóa khóa, rotate khóa hoặc không bao giờ tạo khóa này không thay đổi việc ai được phép gọi API của bạn.
Thứ hai là OLLAMA_API_KEY. Biến này chứa một key do bạn tạo tại https://ollama.com/settings/keys. Client gửi key đó dưới dạng Authorization: Bearer $OLLAMA_API_KEY khi gọi hosted API tại https://ollama.com/api. Đây là credential cho service của họ, được bạn sử dụng với tư cách client. ollama serve của bạn không bao giờ đọc key này. Đặt OLLAMA_API_KEY trên VPS không tạo mật khẩu cho VPS của bạn.
Vì vậy, không có setting nào cần bật. Cả 3 biện pháp bảo vệ dưới đây đều hoạt động theo cùng một cách: giữ cho port không thể truy cập, rồi đặt một thành phần có kiểm tra xác thực ở phía trước port đó.
Kiểm tra các địa chỉ mà server đang listen
sudo ss -tlnp | grep 11434Kết quả an toàn chỉ hiển thị địa chỉ loopback:
LISTEN 0 4096 127.0.0.1:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))Kết quả bị exposed hiển thị mọi interface:
LISTEN 0 4096 0.0.0.0:11434 0.0.0.0:* users:(("ollama",pid=812,fd=3))0.0.0.0 nghĩa là tất cả địa chỉ IPv4 trên máy, bao gồm cả địa chỉ public. *:11434 và [::]:11434 cũng có nghĩa tương tự khi bao gồm IPv6.
Bây giờ hãy xác nhận từ bên ngoài. Chạy lệnh này trên laptop, không chạy trên server:
curl -m 5 http://YOUR_SERVER_IP:11434/api/versioncurl: (28) Connection timed out after 5001 milliseconds là kết quả cần có, và curl: (7) Failed to connect ... Connection refused cũng vậy. Một JSON object có field version nghĩa là bất kỳ ai cũng có thể truy cập toàn bộ API. Test bằng curl ngay trên server không chứng minh được gì, vì loopback luôn trả lời.
Việc expose thường xảy ra theo một trong hai cách. Cách đầu tiên là chỉnh sửa có chủ đích, vì có người cần cho máy thứ hai truy cập model:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"Chỉ một dòng đó đã đủ expose service. Cách thứ hai là Docker, và cách này không yêu cầu bạn chỉnh sửa gì cả. Cách đó có section riêng bên dưới.
Phòng thủ 1: giữ trên localhost và tunnel vào
Hãy ưu tiên cách này. Không cần cài thêm software và không tạo credential nào có thể bị lộ. Port không bao giờ tồn tại trên public interface, nên công cụ quét không thể tìm thấy.
Đặt rõ bind address thay vì phụ thuộc vào giá trị mặc định:
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_HOST=127.0.0.1:11434"Lệnh này ghi vào /etc/systemd/system/ollama.service.d/override.conf. Áp dụng thay đổi rồi kiểm tra:
sudo systemctl daemon-reload
sudo systemctl restart ollama
sudo ss -tlnp | grep 11434ss bây giờ phải hiển thị 127.0.0.1:11434. Nếu vẫn hiển thị 0.0.0.0, một drop-in file khác đang được ưu tiên. Chạy systemctl cat ollama.service để liệt kê unit và mọi drop-in cùng path của chúng, rồi xóa file cũ.
Để dùng model từ laptop, hãy forward port qua SSH:
ssh -N -L 11434:127.0.0.1:11434 you@your-server-L 11434:127.0.0.1:11434 mở port 11434 trên laptop và gửi mọi dữ liệu nhận được tại đó đến 127.0.0.1:11434 theo góc nhìn của server. -N yêu cầu SSH không chạy remote command, nên tiến trình chỉ giữ tunnel mở. Khi tunnel đang chạy, lệnh này hoạt động trên laptop:
curl -s http://localhost:11434/api/tagsBạn sẽ gặp 2 lỗi. bind [127.0.0.1]:11434: Address already in use nghĩa là laptop đang chạy Ollama riêng trên port đó, nên hãy chọn local port khác bằng -L 11500:127.0.0.1:11434 và trỏ client đến 11500. Nếu tunnel kết nối thành công nhưng trả về phản hồi rỗng, nghĩa là SSH đang hoạt động nhưng Ollama không listening ở phía server. Hãy kiểm tra ss trên server trước khi sửa lệnh SSH.
Nếu có nhiều máy client, private network phù hợp hơn một tunnel cho từng người. Đưa các máy vào WireGuard hoặc Tailscale, rồi bind Ollama vào address trên network đó thay vì 0.0.0.0:
[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"Khi đó, port chỉ tồn tại trên interface mà bạn cần key để tham gia. Cách này cũng giảm tác động của lỗi firewall, vì ngay cả khi một rule vô tình cho phép toàn bộ Internet, nó vẫn không thể expose một listener mà public interface không có.
Phòng thủ 2: reverse proxy kiểm tra bearer token
Khi một thành phần trên Internet công cộng phải gọi model, hãy giữ Ollama trên loopback và đặt một proxy phía trước. Proxy xử lý TLS (bảo mật tầng truyền tải) và từ chối các request không có header phù hợp. Ollama vẫn chỉ chấp nhận kết nối từ 127.0.0.1, nên proxy là đường truy cập duy nhất.
Trước tiên, hãy tạo một token thực. Đừng tự nghĩ token bằng tay:
openssl rand -base64 36Một nginx site kiểm tra token đó:
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;
}
}Năm dòng trong đó thực hiện các chức năng quan trọng. Mỗi dòng ngăn một lỗi mà bạn sẽ gặp nếu thiếu nó.
if bên trong block location thường là ý tưởng tồi trong nginx. Tuy nhiên, body có đúng dạng return là một trong hai dạng hoạt động ổn định, nên cách dùng này an toàn.
location = /api/pull là exact match, và nginx ưu tiên exact match hơn prefix location /. Vì vậy, 3 endpoint đó bị từ chối trước khi token được kiểm tra. Token hợp lệ chỉ cho phép inference, không cho phép làm đầy disk.
proxy_set_header Host 127.0.0.1:11434; quan trọng vì Ollama kiểm tra các header Host và Origin nhận vào. Chuyển thẳng public hostname của proxy có thể tạo ra 403 Forbidden xuất phát từ Ollama thay vì nginx, khiến việc debug khó hiểu. OLLAMA_ORIGINS là cần gạt còn lại, dành cho browser client cần cho phép một origin cụ thể.
proxy_buffering off; quan trọng vì Ollama stream response theo từng token. Khi bật buffering, nginx giữ toàn bộ stream rồi chỉ gửi một lần khi kết thúc. Vì vậy client trông như bị treo trong suốt quá trình generation.
proxy_read_timeout 600s; quan trọng vì nginx mặc định là 60 giây. Một generation dài trên CPU dễ vượt quá thời gian này, client nhận 504 Gateway Time-out, còn /var/log/nginx/error.log ghi nhận upstream timed out (110: Connection timed out) while reading response header from upstream. Request vẫn đang chạy. nginx đã từ bỏ request đó.
Reload và kiểm tra cả 2 đường dẫn:
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/tagsLệnh đầu tiên phải in ra 401. Lệnh thứ hai phải in danh sách model của bạn. Nếu lệnh đầu tiên cũng trả về danh sách model, block map đang nằm sai scope. Block này thuộc cấp http, nên hãy đặt nó trong một file bên dưới /etc/nginx/conf.d/ hoặc phía trên block server, không bao giờ đặt bên trong server.
Caddy thực hiện cùng công việc với basic authentication chỉ trong 4 dòng. Cách này phù hợp với browser client hơn bearer token:
llm.example.com {
basic_auth {
apiuser PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:11434
}Chạy caddy hash-password để tạo bcrypt hash mà Caddy yêu cầu. Có một điểm dễ nhầm về tên: directive này là basicauth trước Caddy v2.8 và hiện là basic_auth. Vì vậy, config sao chép từ một hướng dẫn cũ sẽ không load được, và Caddy sẽ báo tên directive mà nó không nhận diện.
Dù chọn proxy nào, đây vẫn là một shared secret cho tất cả mọi người. Mọi client có secret này đều có quyền truy cập giống nhau. Muốn revoke secret, bạn phải sửa config và cập nhật mọi caller cùng lúc.
Biện pháp phòng thủ 3: gateway cấp key riêng cho từng client
Khi có nhiều người hoặc ứng dụng gọi model, một token dùng chung sẽ nhanh chóng không đáp ứng được. Bạn không thể biết client nào tạo ra tải, cũng không thể ngắt riêng một client mà không ngắt tất cả client còn lại. Gateway nằm tại vị trí của proxy trước đó, sử dụng cùng OpenAI-compatible API, cấp một key riêng cho từng client và ghi lại những gì mỗi key đã sử dụng. Gateway LiteLLM tự host là lựa chọn thường dùng. Nó bổ sung budget theo từng key và request log bên cạnh access control.
Quy tắc trong biện pháp phòng thủ 1 không thay đổi. Ollama bind vào 127.0.0.1, gateway là process duy nhất giao tiếp với Ollama, và gateway là service duy nhất có public listener. Gateway trên một máy vẫn mở port 11434 cho toàn Internet chỉ mang tính hình thức, vì caller có thể đi vòng qua gateway.
Bẫy firewall: port container được publish sẽ bỏ qua UFW
Đây là lý do các instance bị expose vẫn tồn tại trên những server mà chủ máy đã cấu hình firewall đúng cách.
UFW (uncomplicated firewall) ghi rule vào chain INPUT của table filter trong kernel, còn INPUT xử lý các packet gửi trực tiếp đến host. Flag -p của Docker ghi một rule destination NAT (network address translation) vào chain PREROUTING của table nat, nơi kernel đánh giá rule trước khi quyết định packet sẽ đi đâu. Khi quyết định routing diễn ra, destination đã được đổi sang địa chỉ của container. Vì vậy packet được forward thay vì delivered locally, rồi đi qua FORWARD thay vì INPUT. Các rule INPUT của UFW không bao giờ được kiểm tra, nên packet đi vòng qua firewall thay vì đi qua firewall.
Đó là lý do chuỗi lệnh này khiến port 11434 mở ra Internet:
sudo ufw default deny incoming
sudo ufw enable
docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollamavà sudo ufw status vẫn báo firewall đang active với chính sách mặc định là deny. Cả hai kết quả đều đúng cùng lúc. Đây chính là lý do người dùng tin vào kết quả sai. Bạn có thể xem rule đã tạo ra việc này:
sudo iptables -t nat -L DOCKER -nCách sửa là thêm một địa chỉ vào publish flag:
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 là dạng viết tắt của -p 0.0.0.0:11434:11434. Khai báo 127.0.0.1 sẽ bind phía host của mapping vào loopback. Vì vậy SSH tunnel và reverse proxy vẫn truy cập được, còn Internet thì không. Recreate container trong trường hợp này an toàn vì các model nằm trong volume có tên ollama, không nằm bên trong container.
Xác nhận rằng cả hai cách kiểm tra đều cho cùng kết quả:
docker port ollama
sudo ss -tlnp | grep 11434docker port ollama phải in ra 11434/tcp -> 127.0.0.1:11434. Nếu in ra 0.0.0.0:11434 thì dịch vụ vẫn đang bị expose. Khi đã hiểu cơ chế này, bạn có thể áp dụng cho mọi container được publish sau này: vì sao port được Docker publish bỏ qua UFW giải thích chain DOCKER-USER và các rule vẫn tồn tại sau khi Docker restart. Nếu bạn vẫn đang xây dựng chính sách cho host, các rule UFW mà VPS mới cần trình bày phần nền tảng cho cấu hình này. Trên Rocky hoặc AlmaLinux không có UFW để cấu hình, nên hãy bắt đầu với chính sách nền tảng tương tự được viết bằng firewalld.
Tiến trình đang chạy dưới tài khoản nào
Script cài đặt Linux tạo một account riêng và chạy service bằng account đó:
useradd -r -s /bin/false -U -m -d /usr/share/ollama ollamaUnit tại /etc/systemd/system/ollama.service đặt User=ollama và Group=ollama. Không thay đổi các thiết lập này. Lệnh ollama serve chạy thủ công trong terminal sẽ chạy dưới tài khoản bạn đã đăng nhập. Nếu tài khoản đó là root, API không yêu cầu xác thực sẽ ghi file dưới quyền root. Kiểm tra tiến trình đang chạy dưới tài khoản nào:
ps -o user= -C ollamaKết quả phải là ollama. Kết quả khác nghĩa là có một tiến trình được khởi động thủ công đang chạy song song với unit hoặc thay thế unit. Cách kiểm tra này áp dụng cho mọi daemon bạn thêm sau này. Hướng dẫn chạy service bằng user có ít quyền nhất xử lý đúng vấn đề này.
Cách kiểm tra endpoint Ollama API có an toàn hay không
Dù bạn chọn cách nào, một bài test sẽ xác nhận kết quả. Bài test đó phải chạy từ một máy khác:
curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tagsCả hai request đều phải timeout hoặc bị từ chối. Nếu bạn đã dựng proxy, hai path tương tự trên hostname của proxy phải trả về 401 khi không có credential và JSON hợp lệ khi có credential.
Sau đó đọc access log một lần. Log cho biết có ai phát hiện ra cổng trong thời gian cổng đó mở hay không:
journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1Ollama ghi một dòng cho mỗi request và có địa chỉ client:
[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"Sau khi Ollama bind vào loopback, mọi dòng phải hiển thị 127.0.0.1 đúng một lần, vì đó là địa chỉ duy nhất mà connection có thể đến từ đó. Nếu cột này có địa chỉ public, request đã đến từ bên ngoài. Timestamp cho biết request xảy ra khi nào. Kết quả bạn cần là command đó không xuất ra dòng nào. Nếu phần model này còn mới với bạn, chạy Ollama trên VPS trình bày cách cài đặt, chọn kích thước model và các giới hạn memory quyết định model nào thực sự có thể load.
FAQ
Ollama có API key hoặc mật khẩu không?
Không. Server bạn chạy không có bất kỳ cơ chế xác thực nào, và tài liệu chính thức nêu rõ rằng không cần xác thực để truy cập API. Cả hai loại được gọi là “Ollama API key” đều phục vụ mục đích khác. Cặp Ed25519 trong /usr/share/ollama/.ollama/ xác thực máy của bạn với ollama.com để bạn có thể push model và pull model private. OLLAMA_API_KEY là credential mà client gửi đến hosted API tại https://ollama.com/api. ollama serve của bạn không đọc cả hai loại này, nên quyền truy cập phải được kiểm soát từ network hoặc từ một proxy đặt phía trước.
OLLAMA_HOST=0.0.0.0 có an toàn nếu tôi có firewall không?
Chỉ khi không có thành phần nào khác ghi firewall rule trên máy đó. 0.0.0.0 có nghĩa là listener thực sự tồn tại trên public interface, và bạn chỉ dựa vào firewall để ngăn truy cập đến nó. Sự tin cậy này mất ngay khi Docker publish một port, vì DNAT rule mà Docker thêm vào table nat được xử lý trước khi packet đến INPUT chain nơi UFW hoạt động. Vì vậy packet được forward và UFW không bao giờ thấy nó. Bind vào 127.0.0.1 hoặc một địa chỉ private của tunnel sẽ loại listener khỏi public interface, nên lỗi firewall cũng không còn gì để expose.
Làm thế nào để kiểm tra port Ollama có mở ra Internet hay không?
Chạy sudo ss -tlnp | grep 11434 trên server và chạy curl -m 5 http://YOUR_SERVER_IP:11434/api/version từ một máy khác. ss hiển thị 127.0.0.1:11434 trong khi curl từ xa bị timeout là kết quả bạn cần. Nếu ss hiển thị 0.0.0.0:11434 hoặc *:11434, đồng thời curl từ xa trả về JSON, thì toàn bộ API đang có thể truy cập. Không bao giờ kiểm tra bằng curl ngay trên server, vì loopback sẽ trả lời bất kể bind address đang được cấu hình như thế nào.
Tôi có thể đổi port từ 11434 sang một port ngẫu nhiên không?
Không, và lý do cần được nói rõ. Đổi port chỉ làm chậm việc scan một port duy nhất, không làm chậm các hoạt động khác. Scanner sẽ quét toàn bộ range, và một request đến /api/tags sẽ nhận diện service bất kể request đến port nào. Đổi port cũng phá vỡ default của mọi client và khiến việc kiểm tra cấu hình của bạn sau này khó hơn. Thay vào đó, hãy bind vào loopback để loại listener khỏi public interface thay vì chỉ chuyển nó sang vị trí khác.
Có người đã truy cập Ollama đang mở của tôi. Tôi cần kiểm tra gì?
Trước tiên, hãy bind nó vào 127.0.0.1 rồi restart service để dừng việc expose trước khi bắt đầu điều tra. Sau đó chạy journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 để xem những địa chỉ bên ngoài nào đã gọi endpoint nào và vào thời điểm nào. Đối chiếu ollama list với các model bạn dự định có, vì /api/pull không yêu cầu xác thực và một model bạn không pull vừa chiếm disk space vừa là bằng chứng. Kiểm tra dung lượng trống bằng df -h. Ollama không ghi lại nội dung prompt ở log level mặc định, nên bạn biết ai đã gửi request và dùng model nào, nhưng không biết model đã generate nội dung gì.