SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-09-06

Host Ollama trên VPS an toàn: chạy LLM self-host

Model 7B cần khoảng 8 GB RAM và chạy 4 đến 10 token/giây trên CPU. Gọi API tại 127.0.0.1:11434/v1, giữ port 11434 đóng để tránh public.

Bạn sẽ xây dựng gì

Một language model có open weight chạy trên server do bạn sở hữu, trả lời qua HTTP API và, nếu muốn, có thêm một trang chat trong browser. Ollama là thành phần tải model, nạp model vào memory và phục vụ request tại http://127.0.0.1:11434. Cài đặt chỉ cần một command. Phần khó nằm ở chỗ khác: chọn model mà VPS của bạn thực sự đủ RAM để chạy, đồng thời không vô tình public một inference server không có authentication ra toàn bộ Internet.

Trước hết, có 2 cảnh báo cần nói rõ. VPS chỉ dùng CPU sẽ chạy các model nhỏ khá chậm, và API hoàn toàn không có authentication tích hợp sẵn. Cả 2 vấn đề này đều được trình bày chi tiết bên dưới vì đây là 2 điểm dễ gây sự cố nhất.

Kiểm tra thực tế về yêu cầu tài nguyên, bằng các con số cụ thể

Dung lượng bộ nhớ của một model xấp xỉ kích thước file, cộng khoảng 1 GB overhead khi chạy, cộng thêm một phần cho context window. Các model mặc định của Ollama được quantize 4-bit (gắn nhãn Q4), tiêu tốn khoảng nửa gigabyte RAM cho mỗi 1 tỷ parameter. Vì vậy, phép tính khá đơn giản và quyết định mọi thứ. Thành phần cuối là phần bạn tự đặt: tăng num_ctx vượt mức mặc định nhỏ của Ollama tạo thêm chỗ cho prompt dài hơn nhưng làm KV cache trong RAM lớn hơn. Hãy chốt context length trước khi đánh giá model có chạy vừa hay không.

Một model 3B như llama3.2:3b có dung lượng tải xuống khoảng 2 GB và cần khoảng 4 GB RAM trống để chạy. Một model 7B hoặc 8B như mistral:7b hoặc llama3.1:8b có dung lượng khoảng 5 GB trên disk và cần khoảng 8 GB RAM, hoặc 16 GB để chạy thoải mái. Một model 13B hoặc 14B cần khoảng 16 GB. Các model trong khoảng 30B đến 70B cần máy có nhiều RAM hoặc, thực tế hơn, cần GPU. Trên VPS chỉ có CPU, model sẽ không vừa hoặc trả lời chậm đến mức không thể sử dụng.

Tiếp theo là tốc độ, vì đây là phần nhiều người đánh giá thấp. Inference trên CPU bị giới hạn bởi memory bandwidth, không phải clock speed, và VPS dùng vCPU dùng chung thường có bandwidth khiêm tốn. Hãy kỳ vọng tốc độ từ một chữ số đến đầu hai chữ số token mỗi giây: model 7-8B Q4 có thể đạt 4 đến 10 token mỗi giây, còn model 3B đạt 10 đến 25. GPU nhanh hơn khoảng một bậc độ lớn. Đây là các con số ước tính có chủ đích. Cách đúng là đo trên chính máy của bạn; bước chạy bên dưới sẽ chỉ cách thực hiện. Hãy tin eval rate của bạn, không phải một con số trong bất kỳ bài viết nào, kể cả bài viết này.

Kết luận thực tế: các model nhỏ đã quantize trên CPU thực sự hữu ích cho việc soạn thảo, tóm tắt và phân loại nếu bạn chấp nhận tốc độ đó. Với model lớn hơn hoặc cần tốc độ cao hơn, hãy dự trù chi phí cho một GPU instance. Nếu muốn xem phép tính này được thực hiện từ đầu đến cuối trên một model cụ thể, chạy Nemotron 3.5 Lightning trên VPS sẽ trình bày tag cần pull, lượng RAM thực tế cần dùng và việc chỉ chạy bằng CPU có đáp ứng được hay không.

Để đối chiếu một model cụ thể với một máy cụ thể, hãy ước tính dung lượng bộ nhớ của model tại đây:

ToolLLM VRAM and model-size calculator

Cài đặt Ollama

Có 2 cách rõ ràng. Script chính thức là cách đơn giản nhất trên VPS trống:

curl -fsSL https://ollama.com/install.sh | sh

Lệnh này tạo system user tên ollama, cài binary vào /usr/local/bin/ollama và đăng ký một systemd service tên ollama.service. Service tự khởi động khi boot và bind vào 127.0.0.1:11434. Xác nhận service đang chạy:

systemctl status ollama
ollama --version

Nếu bạn đã chạy Docker, hãy dùng container:

docker run -d --name ollama \
  -p 127.0.0.1:11434:11434 \
  -v ollama:/root/.ollama \
  --restart always \
  ollama/ollama

Lưu ý tiền tố 127.0.0.1: trong port mapping. Tiền tố này chỉ bind port vào localhost. Nếu viết -p 11434:11434, port sẽ được publish trên mọi interface. Đây là lỗi mà phần bảo mật cảnh báo. Chọn 1 phương thức cài đặt. Không chạy script và container cùng lúc, vì 2 process sẽ tranh chấp port.

Tải và chạy model đầu tiên

ollama pull llama3.2:3b
ollama run llama3.2:3b

pull tải các layer của model xuống disk (model này khoảng 2 GB). run nạp chúng vào memory rồi đưa bạn đến prompt >>>. Nhập một câu hỏi. Token đầu tiên có thể mất vài giây trong khi weights được nạp từ disk vào RAM, sau đó câu trả lời sẽ được stream. Nhập /bye để thoát khỏi chat; Ollama vẫn chạy ở background.

Xem những gì đang được nạp và cách chúng được phân bổ:

ollama ps

Cột PROCESSOR cho biết trạng thái thực tế. 100% CPU nghĩa là không dùng GPU, và đây là nguyên nhân khiến tốc độ chậm. Đo tốc độ thực tế bằng flag verbose:

ollama run --verbose llama3.2:3b "Write two sentences about Linux."

Dòng eval rate được in ở cuối là số token mỗi giây trên phần cứng này. Hãy lấy con số đó làm cơ sở để lập kế hoạch.

Model được lưu ở đâu và cần mua bao nhiêu dung lượng disk

Các model được script cài đặt và service chạy sẽ nằm trong home của user ollama:

sudo du -sh /usr/share/ollama/.ollama/models

Nếu chạy ở chế độ tương tác bằng user của bạn, model nằm trong ~/.ollama/models. Trong container, model nằm trong named volume ollama. Điều này quan trọng vì các weight đã quantize tăng dung lượng rất nhanh: model 3B chiếm khoảng 2 GB, model 7-8B chiếm khoảng 5 GB, model 14B chiếm khoảng 9 GB. Pull 4 model để so sánh là bạn đã dùng 20 GB mà không để ý. Hãy chọn dung lượng disk dựa trên các model bạn định giữ lại, rồi xóa những model còn lại bằng ollama rm <model>. Nếu cùng VPS đó đã chạy một thứ vốn tiêu tốn nhiều dung lượng, chẳng hạn PhotoPrism hoặc Immich đang lưu thư viện ảnh, hãy trừ phần dung lượng đó khỏi free space trước và coi phần còn lại là ngân sách thực tế cho model.

Chạy dưới dạng service do bạn kiểm soát

Script cài đặt đã đăng ký ollama.service, nên service sẽ tự khởi động lại khi boot mà không cần cấu hình thêm. Thiết lập đáng thay đổi là thời gian model tiếp tục nằm trong memory và, trên một số hệ thống, bind address. Cả hai thiết lập này đều đặt trong systemd drop-in để lần nâng cấp Ollama không ghi đè:

sudo systemctl edit ollama.service

Thêm nội dung sau vào dưới header [Service] mà editor hiển thị:

[Service]
Environment="OLLAMA_KEEP_ALIVE=30m"

OLLAMA_KEEP_ALIVE là khoảng thời gian model tiếp tục nằm trong memory sau request cuối cùng (mặc định 5 phút). Tăng giá trị này trên máy được truy vấn cả ngày để tránh phải nạp lại weights mỗi lần; đặt thành 0 trên máy thiếu RAM để giải phóng RAM ngay khi request hoàn tất. Nếu muốn model luôn nằm trong memory thay vì chỉ trong một khoảng thời gian cố định, giữ model Ollama luôn được load trình bày field keep_alive cho từng request và cách làm cho weights được warm lại sau khi reboot thay vì chờ đến request đầu tiên vốn chậm hơn. systemctl edit tự reload các unit file, vì vậy hãy restart để áp dụng thay đổi:

sudo systemctl restart ollama

Điểm bảo mật quan trọng nhất

Theo mặc định, Ollama bind vào 127.0.0.1:11434, nên chỉ các process trên chính VPS mới có thể truy cập nó. Đây là mặc định đúng. Hãy giữ nguyên.

API không có authentication. Hoàn toàn không có. Không có API key, không có đăng nhập, không có rate limit và không có allow-list. Bất kỳ ai truy cập được cổng 11434 đều có thể chạy mọi model bạn đã pull, pull model mới, xóa model và giữ CPU hoặc GPU của bạn chạy ở mức tải tối đa vô thời hạn. Các scanner như Shodan lập chỉ mục hàng nghìn instance Ollama đang mở, và một instance bị expose sẽ bị phát hiện rồi lạm dụng trong vài giờ.

Vì vậy, đây là sai lầm duy nhất tuyệt đối không được mắc phải: không đặt OLLAMA_HOST=0.0.0.0 rồi mở cổng 11434 trên firewall. Việc đó đưa một inference server không có xác thực ra toàn bộ Internet. Không có cấu hình nào khiến 11434 thô trên 0.0.0.0 trở nên an toàn, vì Ollama không có phần nào để cấu hình xác thực; cơ chế xác thực đơn giản là không tồn tại. Đây là quy tắc dành cho service cụ thể này, không phải lệnh cấm mở cổng trong mọi trường hợp: một RustDesk relay tự host cho remote desktop buộc phải nhận traffic public thì mới hoạt động, và điều đó được chấp nhận vì nó có cơ chế xác thực bằng key riêng cùng danh sách cổng ngắn, được tài liệu hóa. Ollama không có bất kỳ thành phần nào như vậy.

Có 3 cách an toàn để truy cập model từ một nơi khác ngoài chính máy đó:

  • Giữ truy cập cục bộ. Nếu caller duy nhất là một chương trình khác trên cùng VPS, một cron script, một bot hoặc một MCP server kết nối các tool của bạn với model, hãy giữ bind ở 127.0.0.1 và để chương trình đó gọi http://127.0.0.1:11434. Không có gì bị expose và không cần thêm gì.
  • Truy cập qua tunnel riêng. Đưa VPS vào một WireGuard VPN do bạn tự host, đặt OLLAMA_HOST thành địa chỉ của tunnel, ví dụ 10.8.0.1, không phải 0.0.0.0, để chỉ các VPN peer mới có thể kết nối. Internet công cộng vẫn không nhìn thấy gì trên cổng 11434.
  • Đặt một reverse proxy có authentication ở phía trước. Terminate TLS và yêu cầu password hoặc token tại nginx, Traefik hoặc Caddy, sau đó proxy đến 127.0.0.1:11434. Ollama vẫn bind vào localhost; proxy là thành phần duy nhất listening trên public port. Mô hình này giống với việc đặt certificate Let's Encrypt trên nginx ở phía trước một local service bất kỳ.

Tùy chọn reverse proxy chính là cách mà chat UI sắp cung cấp cho bạn, kèm theo một đăng nhập thực sự.

Thêm chat UI bằng Open WebUI, phía sau TLS

Open WebUI là một giao diện chat tự host. Chạy nó trong Docker và trỏ đến Ollama cục bộ:

docker run -d \
  --name open-webui \
  --network=host \
  -e OLLAMA_BASE_URL=http://127.0.0.1:11434 \
  -v open-webui:/app/backend/data \
  --restart always \
  ghcr.io/open-webui/open-webui:main

Flag --network=host là chi tiết quan trọng trên Linux VPS. Flag này đặt container vào network namespace của host, nên 127.0.0.1 bên trong container chính là loopback của host và container có thể truy cập Ollama tại 127.0.0.1:11434 mà không cần Ollama lắng nghe trên interface nào khác. Cách cấu hình bridge network mà bạn sẽ thấy ở nơi khác, --add-host=host.docker.internal:host-gateway với OLLAMA_BASE_URL=http://host.docker.internal:11434, không hoạt động trong trường hợp này: tên đó phân giải thành Docker bridge gateway, còn service bind vào 127.0.0.1 trên host thì không thể truy cập qua bridge. Vì vậy, Open WebUI chỉ hiển thị lỗi không thể kết nối đến Ollama.

Đổi lại, host networking khiến Open WebUI lắng nghe trên port 8080 của host ở mọi interface; mọi mapping -p đều bị bỏ qua và Docker sẽ in cảnh báo về việc này. Vì vậy, hãy chặn 8080 ở cả firewall của host và firewall của nhà cung cấp, đồng thời chỉ để TLS reverse proxy là điểm truy cập public duy nhất. Trong lần truy cập đầu tiên, Open WebUI yêu cầu bạn tạo tài khoản admin. Tài khoản này là lớp authentication của bạn, nên hãy chọn password mạnh.

Để mở chat từ laptop qua HTTPS, đặt một TLS reverse proxy phía trước 127.0.0.1:8080. Nếu bạn đã route nhiều Docker app trên máy này, Traefik với TLS tự động cho nhiều app là lựa chọn phù hợp nhất: một block label sẽ cấp certificate và route chat.example.com đến Open WebUI. Cách route theo label cho từng app này cũng được dùng cho mọi browser front-end khác trên máy, dù đó là status dashboard hay một ứng dụng thú vị hơn như Halcyon, hiển thị thư viện Jellyfin như một cửa hàng cho thuê băng đĩa thập niên 90, mỗi ứng dụng dùng một hostname riêng và có login riêng. Quy tắc trong phần bảo mật vẫn được áp dụng: proxy quản lý public port và login, còn Ollama vẫn chạy trên localhost và 8080 của Open WebUI vẫn bị chặn bởi firewall.

Dùng endpoint tương thích với OpenAI trong code

Ollama hỗ trợ một phần OpenAI chat API tại /v1, vì vậy hầu hết thư viện client OpenAI đều hoạt động sau khi thay đổi 2 thứ: base URL và một key dùng tạm.

from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:11434/v1", api_key="ollama")

resp = client.chat.completions.create(
    model="llama3.2:3b",
    messages=[{"role": "user", "content": "Name three Linux distributions."}],
)
print(resp.choices[0].message.content)

Thư viện client yêu cầu api_key nhưng Ollama bỏ qua giá trị này, nên dùng chuỗi nào cũng được. model phải là tên model bạn đã pull trước đó; tên không tồn tại sẽ trả về model "x" not found, try pulling it first. Gọi bằng curl trực tiếp cũng tương tự:

curl http://127.0.0.1:11434/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"llama3.2:3b","messages":[{"role":"user","content":"Hello"}]}'

Đây cũng là cách tích hợp model vào các công cụ agent và editor. Nếu bạn đã phát triển trực tiếp trên máy chủ, model local có thể hỗ trợ script và plugin cùng với Claude Code chạy trên VPS bên trong tmux, giữ các tác vụ soạn thảo riêng tư, chi phí thấp khỏi API tính phí, trong khi phần suy luận nặng vẫn chạy trên model được host.

Các lỗi thường gặp, cùng với chính xác chuỗi bạn sẽ thấy

Tiến trình bị “Killed” giữa chừng khi sinh nội dung. Bạn khởi động một model lớn và terminal in ra Killed, hoặc log của server hiển thị llama runner process has terminated: signal: killed. Linux OOM killer đã dừng tiến trình vì model cần nhiều RAM hơn dung lượng máy có. Xác nhận nguyên nhân bằng sudo dmesg | grep -i oom. Bạn sẽ thấy một dòng như Out of memory: Killed process ... (ollama). Cách xử lý là dùng model nhỏ hơn hoặc quantized mạnh hơn, dùng llama3.2:3b thay vì 13B, hoặc thêm swap để một tác vụ chỉ vượt RAM vật lý một chút có thể chạy chậm rồi hoàn tất thay vì bị dừng. Swap biến lỗi crash ngay lập tức thành câu trả lời chậm. Nó không biến model 70B thành lựa chọn thực tế trên máy có 4 GB. Việc kill diễn ra im lặng nếu bạn không tình cờ theo dõi terminal. Vì vậy, trên một máy chủ mà bạn truy vấn từ nơi khác, hãy gắn một unit OnFailure= vào ollama.service để gửi thông báo đến một ntfy server bạn tự host để nhận push alert. Bạn sẽ biết ngay khi tiến trình dừng, thay vì chỉ phát hiện ở request tiếp theo.

"Error: model requires more system memory". Ollama từ chối khởi động model và in ra Error: model requires more system memory (X GiB) than is available (Y GiB). Đây là phiên bản có kiểm soát của lỗi crash ở trên: Ollama tự tính toán rồi dừng lại, thay vì để OOM killer xử lý. Ollama còn cung cấp cho bạn 2 con số. Chọn model có yêu cầu thấp hơn lượng RAM còn trống (kiểm tra bằng free -h), giảm context length hoặc chuyển sang VPS lớn hơn. Không có flag nào khiến model vừa với bộ nhớ khi bộ nhớ cần thiết là có thật.

Token đầu tiên mất rất nhiều thời gian, sau đó hoạt động bình thường. Model đang cold không in ra gì trong 5 đến 30 giây, rồi stream bình thường. Đó là lúc weights được load lần đầu từ disk vào RAM, và storage chậm sẽ làm thời gian này dài hơn. Sau khi được load, model tiếp tục nằm trong RAM trong thời gian của OLLAMA_KEEP_ALIVE, nên prompt thứ hai được trả lời ngay. Nếu lần load đầu tiên lâu hơn timeout ở bất kỳ điểm nào trong call path, bạn sẽ nhận lỗi thay vì một câu trả lời chậm. Xác định layer nào báo lỗi context deadline exceeded sẽ cho biết client, proxy hay quá trình load đã hết thời gian chờ. Tăng giá trị đó nếu khoảng chờ gây khó chịu, và dùng ollama ps để kiểm tra model hiện có đang được load hay không.

Mọi thứ đều chậm. Tốc độ là 10 token mỗi giây hoặc thấp hơn, nhưng hoàn toàn không có lỗi. Đó là CPU inference đang hoạt động đúng như CPU inference. ollama ps hiển thị 100% CPU, nghĩa là không có GPU. Đây không phải bug và không có setting nào sửa được, vì giới hạn nằm ở memory bandwidth chứ không phải cấu hình sai. Hãy dùng model nhỏ hơn, chấp nhận tốc độ này hoặc chuyển sang GPU instance. Trước khi kết luận có vấn đề, hãy đo tốc độ thực tế bằng --verbose. Khi thời gian chờ đến từ độ dài câu trả lời thay vì tốc độ sinh token, giới hạn câu trả lời bằng num_predict sẽ ngăn model có xu hướng dài dòng tiêu tốn nhiều phút cho những token bạn vốn không định đọc.

Kết nối từ máy khác bị từ chối. Từ laptop, bạn nhận được curl: (7) Failed to connect to <ip> port 11434: Connection refused. Đây là hành vi đúng theo thiết kế: Ollama chỉ bind vào localhost. Đừng "sửa" bằng cách bind vào 0.0.0.0, vì đó chính là lỗi expose đã nói ở trên. Hãy truy cập model qua VPN hoặc thông qua proxy có authentication.

Bạn đã expose 11434 ra Internet. Nếu bạn đã đặt OLLAMA_HOST=0.0.0.0, mở firewall và hiện thấy các lần pull model mà bạn không khởi tạo, hoặc CPU bị ghim ở mức 100% bởi các client không xác định, nghĩa là máy đã bị phát hiện và bị người khác sử dụng. Đây là lỗi nghiêm trọng, không phải trường hợp hiếm. Bind lại vào 127.0.0.1 hoặc địa chỉ VPN, đóng 11434 trên firewall và đặt authentication ở phía trước. Hãy giả định rằng mọi thứ có thể truy cập tại địa chỉ đó trong thời gian cổng mở đều đã bị người lạ gửi query.

Sao lưu và nâng cấp

Không có nhiều state cần giữ lại. Các model có thể tải lại, nên chỉ cần sao lưu data volume, account, lịch sử chat, settings của Open WebUI và mọi systemd drop-in bạn đã tạo. Sao lưu volume bằng một container dùng tạm:

docker run --rm -v open-webui:/data -v "$PWD":/backup alpine \
  tar czf /backup/open-webui.tgz -C /data .

Nâng cấp Ollama bằng cách chạy lại install script; nâng cấp Open WebUI bằng docker pull ghcr.io/open-webui/open-webui:main rồi tạo lại container. Không nên pin phiên bản trong thời gian dài: chất lượng model và runtime thay đổi nhanh, nên hãy đọc release notes và benchmark lại trên chính máy của bạn thay vì tin vào các số liệu của quý trước.

FAQ

Tôi có thực sự chạy được LLM trên VPS chỉ có CPU không?

Có, nhưng có giới hạn. Các model nhỏ đã quantize trong khoảng 3B đến 8B chạy được trên CPU và hữu ích cho việc soạn thảo, tóm tắt và phân loại, chỉ là tốc độ chậm, khoảng từ vài token mỗi giây đến mức thấp của khoảng hai chữ số trên một vCPU dùng chung. Model từ 13B trở lên chạy rất chậm hoặc hoàn toàn không vừa RAM. Nếu cần tốc độ thực tế hoặc model lớn hơn, bạn cần một GPU instance.

Mỗi model cần bao nhiêu RAM?

Với các model mặc định đã quantize 4-bit, có thể ước tính khoảng 0.5 GB RAM cho mỗi tỷ parameter của weights, cộng khoảng 1 GB overhead và thêm một ít cho context. Vì vậy, model 3B cần khoảng 4 GB RAM trống, model 7-8B cần khoảng 8 GB, còn model 14B cần khoảng 16 GB. Kiểm tra phần RAM còn dư bằng free -h và chừa đủ cho operating system cùng các tiến trình khác trên máy.

Ollama API có authentication không?

Không. Ollama không có authentication tích hợp, API key hoặc rate limit. Bất kỳ ai truy cập được port 11434 đều có toàn quyền điều khiển nó. Đây chính là lý do Ollama bind vào 127.0.0.1 theo mặc định và bạn tuyệt đối không được expose 11434 trên 0.0.0.0 ra Internet. Chỉ truy cập nó locally, qua private VPN hoặc qua reverse proxy có thêm bước đăng nhập.

Làm cách nào để thêm web chat interface?

Chạy Open WebUI trong Docker với --network=host để nó dùng chung loopback của host và truy cập Ollama native tại http://127.0.0.1:11434, sau đó đặt một TLS reverse proxy phía trước port 8080 để truy cập từ laptop. Giữ 8080 đóng trên firewall để proxy là điểm truy cập public duy nhất. Admin account của Open WebUI cung cấp thông tin đăng nhập; bạn đặt password cho account này trong lần khởi chạy đầu tiên.

Làm cách nào để gọi nó từ application của tôi?

Sử dụng endpoint tương thích với OpenAI tại http://127.0.0.1:11434/v1. Trỏ bất kỳ OpenAI SDK nào đến base URL đó, truyền một chuỗi bất kỳ làm API key vì key này sẽ bị bỏ qua, rồi đặt model thành tên của model bạn đã pull. Code OpenAI hiện có thường chạy mà không cần thay đổi, ngoài base URL và key.