SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor

Ollama API không có password: Cách bảo vệ port 11434

Ollama không có user, password hay API key. Bất kỳ ai truy cập được port 11434 đều có thể chạy, tải và xóa model. Xem 3 cách xử lý theo thứ tự.

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 ở bất kỳ đâu. Bất kỳ thiết bị nào có thể mở kết nối TCP đến port 11434 đều có thể liệt kê 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ộ bao hàm toàn bộ mô hình bảo mật. Ollama bind vào 127.0.0.1 theo mặc định, nên trên laptop, loopback interface chính là cơ chế kiểm soát truy cập. Khi chuyển listener sang một địa chỉ public, cơ chế kiểm soát truy cập biến mất vì không có gì 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. Đây cũng là thay đổi loại bỏ toàn bộ cơ chế bảo vệ cùng lúc.

Cổng 11434 bị public làm lộ những gì

Toàn bộ endpoint đều bị lộ. 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"}'

Xét theo 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ó giới hạn CPU theo chính sách fair-use, tải cao kéo dài đồng nghĩa allowance của bạn bị người lạ tiêu tốn. Kiểm soát chi phí workload AI trên VPS sẽ khó hơn nhiều khi bạn không phải người gọi duy nhất.
  • /api/pull ghi 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, và 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, nên bạn thấy endpoint, status, latency và địa chỉ client, chứ không thấy nội dung prompt. Dù vậy, đây vẫn là bản ghi cho biết ai đã dùng máy của bạn và dùng vào việc gì, nằm trong journal, trong khi bạn không chủ động chọn thu thập thông tin đó.
  • /api/delete xóa model. Muốn lấy lại, bạn phải download chúng lần nữa bằng băng thông của chính mình.

Không điều nào trong số này cần đến exploit. Đâ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 khái niệm khác nhau. Cả hai đều không phải là mật khẩu cho server của bạn. 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, script cài đặt tạo một system user tên là ollama với thư mục home tại /usr/share/ollama, nên cặp khóa nằm ở đây:

/usr/share/ollama/.ollama/id_ed25519
/usr/share/ollama/.ollama/id_ed25519.pub

Khóa này dùng cho kết nối đi ra ngoài. ollama signin đăng ký phần public với tài khoản ollama.com của bạn. Ollama dùng khóa này để xác thực quyền push model lên registry hoặc pull model private. Nó xác minh 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 tạo khóa không làm 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 vai trò 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 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 nguyên tắc: giữ cho port không thể truy cập, rồi đặt một lớp phía trước port đó để thực hiện kiểm tra.

Kiểm tra hiện tại server đang lắng nghe trên địa chỉ nào

sudo ss -tlnp | grep 11434

Kết quả an toàn 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ị expose 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 có nghĩa là tất cả địa chỉ IPv4 trên máy, bao gồm cả địa chỉ public. *:11434[::]: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/version

curl: (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ó trường version cho biết toàn bộ API có thể được bất kỳ ai yêu cầu và truy cập. Việc test bằng curl ngay trên server không chứng minh được gì, vì loopback luôn phản hồi.

Việc expose thường xảy ra theo một trong hai cách. Cách đầu tiên là sửa cấu hình có chủ đích, vì ai đó cần một máy khác truy cập model:

sudo systemctl edit ollama.service
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"

Chỉ một dòng đó đã đủ expose dịch vụ. Cách thứ hai là Docker, và cách này không yêu cầu bạn sửa gì cả. Cách đó có section riêng bên dưới.

Phương án 1: giữ trên localhost và tạo tunnel vào

Hãy ưu tiên cách này. Bạn không cần cài thêm phần mềm và không tạo credential nào có thể bị lộ. Cổng không bao giờ tồn tại trên interface public, nên công cụ scan không thể tìm thấy nó.

Đặt rõ địa chỉ bind thay vì dựa 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 11434

ss hiện phải hiển thị 127.0.0.1:11434. Nếu vẫn hiển thị 0.0.0.0, một file drop-in thứ hai đang được ưu tiên. Chạy systemctl cat ollama.service để liệt kê unit và mọi drop-in kèm đường dẫn của chúng, rồi xóa file cũ.

Để dùng model từ laptop, forward cổng qua SSH:

ssh -N -L 11434:127.0.0.1:11434 you@your-server

-L 11434:127.0.0.1:11434 mở cổng 11434 trên laptop và gửi mọi dữ liệu đến đó tới 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/tags

Bạn sẽ gặp 2 lỗi sau. bind [127.0.0.1]:11434: Address already in use nghĩa là laptop đang chạy Ollama riêng trên cổng đó, nên hãy chọn một cổng local khác bằng -L 11500:127.0.0.1:11434 và trỏ client tới 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 hoạt động còn 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 việc tạo 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 địa chỉ của nó trên network đó thay vì 0.0.0.0:

[Service]
Environment="OLLAMA_HOST=10.8.0.1:11434"

Khi đó, cổng chỉ tồn tại trên một interface mà bạn cần key để tham gia. Cách này cũng vẫn an toàn trước lỗi firewall, vì một rule vô tình cho phép toàn bộ Internet vẫn không thể expose listener mà interface public không sở hữu.

Biện pháp bảo vệ 2: reverse proxy kiểm tra bearer token

Khi một hệ thống trên Internet công khai 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 termination 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à tuyến 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 36

Mộ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ông việc thực sự, và mỗi dòng ngăn một lỗi mà nếu không có thì bạn sẽ gặp phải.

if bên trong block location thường là ý tưởng không tốt trong nginx, nhưng body có đú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 cao hơn prefix location /, nên 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 HostOrigin gửi đến. Chuyển thẳng hostname public của proxy có thể tạo ra một 403 Forbidden do Ollama trả về 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ữ stream rồi gửi toàn bộ một lần ở cuối, nên client của bạn 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ễ dàng 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 đã ngừng chờ request đó.

Reload và kiểm tra cả hai đườ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/tags

Lệnh đầu tiên phải in 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. Nó phải nằm ở cấp http, vì vậy hãy đặt nó trong một file dưới /etc/nginx/conf.d/ hoặc bên trên block server, không bao giờ đặt bên trong server.

Caddy thực hiện cùng công việc bằng basic authentication trong 4 dòng, 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 bẫy về tên: directive này là basicauth trước Caddy v2.8 và hiện là basic_auth, vì vậy config chép từ một hướng dẫn cũ sẽ không load được và Caddy sẽ nêu tên directive mà nó không nhận dạng.

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, và thu hồi nó nghĩa là sửa config rồi cập nhật tất cả caller cùng lúc.

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 còn phù hợp. Bạn không thể biết client nào gây ra tải, cũng không thể ngắt một client mà không ngắt tất cả client còn lại. Gateway nằm ở 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 mỗi key đã sử dụng những gì. Gateway LiteLLM tự host là lựa chọn thường dùng. Nó bổ sung ngân sách theo từng key và log request bên cạnh quyền kiểm soát truy cập.

Quy tắc trong 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ỉ là hình thức, vì client có thể đơn giản đi vòng qua gateway.

Bẫy firewall: cổng container được publish 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ủ sở hữu đã cấu hình firewall đúng cách.

UFW (uncomplicated firewall) ghi các rule của nó vào chain INPUT của table filter trong kernel, còn INPUT xử lý các packet có đích là chính 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 này trước khi quyết định packet sẽ đi đâu. Khi quyết định định tuyến diễn ra, đích của packet đã được đổi thành địa chỉ của container. Vì vậy packet được forward thay vì phân phối cục bộ, và đ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 sau để port 11434 mở cho Internet:

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 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 mọi người 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 -n

Cách sửa là chỉ định một địa chỉ trong flag publish:

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. Việc chỉ định 127.0.0.1 bind phía host của mapping vào loopback. Vì vậy SSH tunnel và reverse proxy của bạn vẫn truy cập được, còn Internet thì không. Tạo lại container an toàn trong trường hợp này 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 11434

docker port ollama phải in ra 11434/tcp -> 127.0.0.1:11434. Nếu in ra 0.0.0.0:11434 thì bạn vẫn đang expose dịch vụ. Khi đã hiểu cơ chế này, bạn có thể áp dụng cho mọi container được publish: vì sao các port Docker publish lại bỏ qua UFW trình bày về chain DOCKER-USER và các rule vẫn còn sau khi Docker restart. Nếu bạn vẫn đang xây dựng chính sách của host, các rule UFW cần có trên một VPS mới trình bày phần nền tảng cho cấu hình này.

Tiến trình đang chạy với tài khoản nào

Script cài đặt Linux tạo một tài khoản riêng và chạy service bằng tài khoản đó:

useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama

Unit tại /etc/systemd/system/ollama.service đặt User=ollamaGroup=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 với tài khoản bạn đã đăng nhập. Nếu tài khoản đó là root, API không có xác thực sẽ ghi file với quyền root. Kiểm tra tài khoản đang được sử dụng:

ps -o user= -C ollama

Kết quả phải là ollama. Nếu khá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. Chạy service với tài khoản có quyền tối thiểu sẽ thực hiện đúng việc này.

Cách kiểm tra endpoint Ollama API có an toàn không

Dù bạn chọn phương án nào, một bài kiểm tra sau sẽ xác nhận kết quả. Bài kiểm tra phải được 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/tags

Cả hai lệnh đều phải hết thời gian chờ hoặc bị từ chối. Nếu bạn đã dựng proxy, hai đường dẫn tương tự trên hostname của proxy phải trả về 401 khi không có credentials và trả về JSON hợp lệ khi có credentials.

Sau đó hãy đọc access log một lần. Log cho biết có ai phát hiện ra cổng này trong thời gian nó còn mở hay không:

journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1

Ollama ghi một dòng cho mỗi request và bao gồm đị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à kết nối có thể đến từ đó. Nếu cột này chứa địa chỉ public, request đã đến từ bên ngoài. Timestamp cho biết request xảy ra lúc nào. Kết quả bạn cần là lệnh đó không trả về output nào. Nếu phần model 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 bộ nhớ quyết định model nào thực sự có thể được 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. Hai thứ được gọi là “Ollama API key” thực ra dùng theo hướng 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ái nào trong hai thứ đó, nên quyền truy cập phải do network hoặc proxy phía trước kiểm soát.

OLLAMA_HOST=0.0.0.0 có an toàn nếu tôi dùng firewall không?

Chỉ khi không có thành phần nào khác ghi rule firewall trên máy đó. 0.0.0.0 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ì rule DNAT mà Docker thêm vào table nat được xử lý trước khi packet đến chain INPUT, nơi UFW hoạt động. Vì vậy packet được forward và UFW không bao giờ nhìn thấy nó. Bind vào 127.0.0.1 hoặc một địa chỉ tunnel private sẽ loại listener khỏi public interface, nên dù firewall cấu hình sai cũng không còn listener để lộ ra ngoài.

Làm cách nào để kiểm tra port Ollama có mở ra Internet không?

Chạy sudo ss -tlnp | grep 11434 trên server và 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 được. 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 là gì.

Tôi chỉ cần chuyển port từ 11434 sang một port ngẫu nhiên là được không?

Không, và lý do cần được nói rõ. Đổi port chỉ làm chậm việc quét đúng một port duy nhất. Scanner sẽ quét toàn bộ range, và một request đến /api/tags sẽ xác định được service bất kể request đến port nào. Đổi port cũng làm hỏng default của mọi client và khiến cấu hình của bạn khó hiểu hơn về sau. Thay vào đó, hãy bind vào loopback. Cách này 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, bind nó vào 127.0.0.1 rồi restart service để dừng việc lộ ra ngoài 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. So sánh 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 mà bạn không pull vừa chiếm dung lượng đĩa, 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à gửi cho model nào, nhưng không biết model đã tạo ra nội dung gì.