SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor

วิธีตั้งค่ารหัสผ่าน Ollama API ป้องกันพอร์ต 11434

Ollama API ไม่มีระบบตรวจสอบสิทธิ์โดยค่าเริ่มต้น ทำให้พอร์ต 11434 ที่เปิดสาธารณะมีความเสี่ยงสูงต่อการถูกสั่งรันหรือลบโมเดล เรียนรู้ 3 ขั้นตอนการตั้งค่าความปลอดภัยที่ถูกต้อง

Ollama API ไม่มีรหัสผ่าน

Ollama API ไม่มีการตรวจสอบสิทธิ์ (authentication) ใดๆ ทั้งสิ้น บนเซิร์ฟเวอร์ที่คุณใช้งานไม่มีทั้งผู้ใช้ ไม่มีรหัสผ่าน ไม่มีการตรวจสอบคีย์ และไม่มีรายการอนุญาต (allowlist) ใดๆ ทั้งสิ้น ทุกสิ่งที่สามารถเปิดการเชื่อมต่อ TCP ไปยังพอร์ต 11434 ได้ จะสามารถแสดงรายการโมเดล สั่งรันโมเดล ดาวน์โหลดโมเดลใหม่ และลบโมเดลที่คุณมีอยู่ได้ทั้งหมด

เอกสารอย่างเป็นทางการระบุไว้อย่างชัดเจนว่า: "ไม่จำเป็นต้องมีการตรวจสอบสิทธิ์เมื่อเข้าถึง Ollama API ในเครื่องผ่าน http://localhost:11434" คำว่า ในเครื่อง (locally) คือรูปแบบความปลอดภัยทั้งหมดของระบบนี้ โดยค่าเริ่มต้น Ollama จะผูก (bind) เข้ากับ 127.0.0.1 ดังนั้นบนแล็ปท็อป อินเทอร์เฟซ loopback จึงทำหน้าที่เป็นตัวควบคุมการเข้าถึง หากคุณย้ายการรับฟัง (listener) ไปยังที่อยู่สาธารณะ ตัวควบคุมการเข้าถึงก็จะหายไปทันที เพราะไม่มีสิ่งใดมาทดแทน

นี่คือเหตุผลว่าทำไมเรื่องนี้จึงสำคัญบน VPS (virtual private server) ค่าเริ่มต้นนั้นมีความปลอดภัย แต่การเปลี่ยนแปลงแรกที่คนส่วนใหญ่ทำ คือการเปิดการรับฟังเพื่อให้เครื่องที่สองสามารถใช้งานโมเดลได้ ซึ่งเป็นการเปลี่ยนแปลงที่ลบการป้องกันทุกอย่างออกไปในทันที

สิ่งที่พอร์ต 11434 ที่เปิดทิ้งไว้เปิดเผยออกมา

ทุก endpoint ทั้งหมด ไม่มีโหมดอ่านอย่างเดียวและไม่มีพอร์ตสำหรับผู้ดูแลระบบแยกต่างหาก นี่คือคำขอจริงที่มุ่งเป้าไปยังที่อยู่เซิร์ฟเวอร์แทนที่จะเป็น 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 ของคุณประมวลผล inference ให้คนอื่น ในแผนบริการที่มีการจำกัดการใช้งาน CPU อย่างเป็นธรรม ภาระงานที่ต่อเนื่องหมายถึงโควตาของคุณกำลังถูกใช้โดยคนแปลกหน้า และ การควบคุมค่าใช้จ่ายภาระงาน AI บน VPS จะทำได้ยากขึ้นมากเมื่อคุณไม่ใช่ผู้เรียกใช้งานเพียงคนเดียว
  • /api/pull เขียนข้อมูลลงในดิสก์ของคุณ โมเดลแต่ละตัวมีขนาดตั้งแต่ 2 ถึง 40 กิกะไบต์ การวนซ้ำของการดึงข้อมูลจะทำให้พื้นที่จัดเก็บเต็ม และดิสก์ที่เต็มจะทำให้บริการอื่นทุกอย่างบนเครื่องหยุดทำงาน ไม่ใช่แค่ Ollama เท่านั้น
  • คำขอจะเข้ามาภายในกระบวนการของคุณและถูกบันทึกไว้ ในระดับ log เริ่มต้น Ollama จะบันทึกเฉพาะ metadata เท่านั้น ดังนั้นคุณจะได้ endpoint, สถานะ, latency และที่อยู่ของไคลเอนต์ ไม่ใช่ข้อความ prompt แต่นั่นก็ยังคงเป็นบันทึกว่าใครใช้เครื่องของคุณและใช้เพื่ออะไร ซึ่งค้างอยู่ใน journal ของคุณ โดยที่คุณไม่ได้เป็นผู้เลือกที่จะจัดเก็บมัน
  • /api/delete ลบโมเดลทิ้ง การจะนำโมเดลกลับมาหมายถึงการต้องดาวน์โหลดใหม่โดยใช้แบนด์วิดท์ของคุณเอง

สิ่งเหล่านี้ไม่จำเป็นต้องอาศัยช่องโหว่ใดๆ มันคือ API ที่มีเอกสารกำกับซึ่งทำงานตรงตามที่ออกแบบไว้ทุกประการ

คีย์ Ed25519 ไม่ใช่การควบคุมการเข้าถึง

เมื่อค้นหาคำว่า "Ollama API key" คุณจะพบกับสองสิ่งที่แตกต่างกัน ทั้งสองอย่างไม่ใช่รหัสผ่านสำหรับเซิร์ฟเวอร์ของคุณ การแยกแยะความแตกต่างเหล่านี้จะช่วยลดความสับสนได้เป็นอย่างดี

อย่างแรกคือคู่กุญแจยืนยันตัวตน (identity key pair) Ollama จะสร้างคู่กุญแจ Ed25519 ขึ้นมาในการรันครั้งแรก บน Linux สคริปต์ติดตั้งจะสร้างผู้ใช้ระบบชื่อ ollama โดยมีไดเรกทอรีหลักอยู่ที่ /usr/share/ollama ดังนั้นคู่กุญแจจะอยู่ที่นี่:

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

คีย์ดังกล่าวชี้ออกไปยังภายนอก ollama signin จะลงทะเบียนส่วนที่เป็น public key เข้ากับบัญชี ollama.com ของคุณ ซึ่งเป็นสิ่งที่อนุญาตให้คุณ push โมเดลไปยัง registry หรือ pull โมเดลส่วนตัวออกมาได้ มันทำหน้าที่พิสูจน์ตัวตนเครื่องของคุณต่อ 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 address:

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/version

curl: (28) Connection timed out after 5001 milliseconds คือคำตอบที่คุณต้องการ และ curl: (7) Failed to connect ... Connection refused ก็เช่นกัน หากได้รับออบเจกต์ JSON ที่มีฟิลด์ version แสดงว่า API ทั้งหมดสามารถเข้าถึงได้โดยใครก็ตามที่ร้องขอ การทดสอบด้วย curl บนตัวเซิร์ฟเวอร์เองนั้นไม่สามารถพิสูจน์อะไรได้ เพราะ loopback จะตอบกลับเสมอ

การเปิดเผยข้อมูลมักเกิดขึ้นจาก 2 สาเหตุหลัก สาเหตุแรกคือการแก้ไขไฟล์โดยเจตนา เนื่องจากมีความจำเป็นต้องให้เครื่องที่สองเข้าถึงโมเดลได้:

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

บรรทัดเดียวนั้นคือจุดที่ทำให้เกิดการเปิดเผยทั้งหมด สาเหตุที่สองคือ Docker ซึ่งไม่ได้แจ้งให้คุณแก้ไขไฟล์ใดๆ เลย และประเด็นนั้นมีส่วนอธิบายแยกต่างหากในหัวข้อด้านล่างนี้

การป้องกันที่ 1: จำกัดไว้ที่ localhost และใช้ tunnel เข้าไป

ให้ใช้วิธีนี้เป็นอันดับแรก เนื่องจากไม่ต้องติดตั้งซอฟต์แวร์ใหม่และไม่สร้างข้อมูลรับรองที่อาจรั่วไหลได้ พอร์ตจะไม่ปรากฏบนอินเทอร์เฟซสาธารณะ ทำให้การสแกนหาพอร์ตไม่พบ

กำหนด bind address ให้ชัดเจนแทนการใช้ค่าเริ่มต้น:

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 11434

ss ควรแสดงผลเป็น 127.0.0.1:11434 หากยังแสดงเป็น 0.0.0.0 แสดงว่ามีไฟล์ drop-in อื่นที่ถูกนำมาใช้แทน ให้รัน systemctl cat ollama.service เพื่อแสดงรายการ unit และไฟล์ drop-in ทั้งหมดพร้อมพาธ จากนั้นให้ลบไฟล์ที่ไม่ได้ใช้งานออก

หากต้องการใช้งานโมเดลจากแล็ปท็อปของคุณ ให้ทำ port forwarding ผ่าน 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 ไม่ต้องรันคำสั่งระยะไกล เพื่อให้กระบวนการคงสถานะ tunnel ไว้ ในขณะที่รันคำสั่งนี้ คุณจะสามารถใช้งานบนแล็ปท็อปได้ดังนี้:

curl -s http://localhost:11434/api/tags

คุณอาจพบความล้มเหลว 2 ประการ ประการแรกคือ bind [127.0.0.1]:11434: Address already in use ซึ่งหมายความว่าแล็ปท็อปของคุณกำลังรัน Ollama ของตัวเองบนพอร์ตนั้นอยู่ ให้เลือกพอร์ต local อื่นด้วย -L 11500:127.0.0.1:11434 แล้วชี้ไคลเอนต์ของคุณไปที่ 11500 ประการที่สองคือการตอบกลับที่ว่างเปล่าผ่าน tunnel ที่เชื่อมต่อได้ปกติ ซึ่งหมายความว่า SSH ทำงานได้ดี แต่ Ollama ไม่ได้ฟังพอร์ตที่ฝั่งเซิร์ฟเวอร์ ให้ตรวจสอบ ss ที่นั่นก่อนที่จะแก้ไขคำสั่ง SSH

สำหรับเครื่องไคลเอนต์หลายเครื่อง การใช้เครือข่ายส่วนตัว (private network) จะดีกว่าการทำ tunnel แยกรายบุคคล ให้เชื่อมต่อเครื่องเหล่านั้นเข้ากับ WireGuard หรือ Tailscale จากนั้นจึง bind Ollama เข้ากับที่อยู่บนเครือข่ายนั้นแทนที่จะเป็น 0.0.0.0:

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

พอร์ตจะปรากฏเฉพาะบนอินเทอร์เฟซที่คุณต้องมีกุญแจในการเข้าถึงเท่านั้น วิธีนี้ยังช่วยป้องกันความผิดพลาดจาก firewall ได้ด้วย เพราะหากมีการตั้งค่ากฎที่เปิดให้คนภายนอกเข้าถึงได้โดยไม่ตั้งใจ ก็ยังไม่สามารถเปิดเผย listener ที่ไม่ได้อยู่บนอินเทอร์เฟซสาธารณะได้

การป้องกันที่ 2: การใช้ reverse proxy ตรวจสอบ bearer token

เมื่อจำเป็นต้องให้บริการภายนอกเรียกใช้งานโมเดล ให้คงการตั้งค่า Ollama ไว้ที่ loopback และวาง proxy ไว้ด้านหน้าเพื่อทำหน้าที่เป็นด่านหน้า proxy จะทำหน้าที่ยุติ TLS (transport layer security) และปฏิเสธคำขอที่ไม่มี header ที่ถูกต้อง โดย Ollama จะยังคงยอมรับการเชื่อมต่อจาก 127.0.0.1 เท่านั้น ดังนั้น proxy จึงเป็นเส้นทางเดียวที่เข้าถึงได้

ขั้นแรกให้สร้าง token จริงขึ้นมา ห้ามสร้างขึ้นเองด้วยมือ:

openssl rand -base64 36

ตัวอย่างการตั้งค่า nginx เพื่อตรวจสอบ 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;
    }
}

มี 5 บรรทัดในตัวอย่างที่ทำหน้าที่สำคัญ และแต่ละบรรทัดช่วยป้องกันความล้มเหลวที่คุณอาจพบเจอได้หากไม่ตั้งค่าไว้

การใช้ if ภายในบล็อก location โดยปกติแล้วไม่ใช่แนวทางที่ดีใน nginx แต่การระบุ body เป็น return เพียงอย่างเดียวเป็นรูปแบบหนึ่งในสองรูปแบบที่ทำงานได้อย่างคาดการณ์ได้ ดังนั้นการใช้งานนี้จึงปลอดภัย

location = /api/pull คือการจับคู่แบบตรงตัว (exact match) และ nginx จะให้ลำดับความสำคัญของการจับคู่แบบตรงตัวสูงกว่าการจับคู่แบบ prefix ด้วย location / ดังนั้น endpoint ทั้งสามรายการนี้จะถูกปฏิเสธก่อนที่จะมีการตรวจสอบ token เสียอีก การมี token ที่ถูกต้องจึงหมายถึงการได้รับสิทธิ์ในการประมวลผล ไม่ใช่สิทธิ์ในการเขียนข้อมูลจนเต็มดิสก์ของคุณ

proxy_set_header Host 127.0.0.1:11434; มีความสำคัญเนื่องจาก Ollama จะตรวจสอบ header Host และ Origin ที่ส่งเข้ามา การส่งผ่าน hostname สาธารณะของ proxy ไปโดยตรงอาจทำให้เกิด 403 Forbidden ที่มาจาก Ollama แทนที่จะมาจาก nginx ซึ่งจะทำให้การตรวจสอบปัญหาทำได้ยาก ส่วน OLLAMA_ORIGINS เป็นอีกหนึ่งตัวเลือกสำหรับ client ประเภทเบราว์เซอร์ที่ต้องการอนุญาต origin เฉพาะเจาะจง

proxy_buffering off; มีความสำคัญเพราะ Ollama จะส่งการตอบกลับแบบ stream ทีละ token หากเปิดใช้งานการ buffering ไว้ nginx จะกักเก็บ stream นั้นไว้และส่งมอบให้ทั้งหมดในคราวเดียวเมื่อเสร็จสิ้น ทำให้ client ของคุณดูเหมือนค้างไปตลอดระยะเวลาการสร้างข้อความ

proxy_read_timeout 600s; มีความสำคัญเนื่องจาก nginx มีค่าเริ่มต้นอยู่ที่ 60 วินาที การสร้างข้อความที่ยาวบน CPU อาจใช้เวลานานกว่านั้น ทำให้ client ได้รับ 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 อยู่ในขอบเขต (scope) ที่ผิด ซึ่งควรอยู่ในระดับ http ดังนั้นให้วางไว้ในไฟล์ภายใต้ /etc/nginx/conf.d/ หรือเหนือบล็อก server ห้ามวางไว้ภายใน server โดยเด็ดขาด

Caddy สามารถทำงานเดียวกันนี้ได้ด้วยการทำ basic authentication โดยใช้เพียง 4 บรรทัด ซึ่งเหมาะกับ client ประเภทเบราว์เซอร์มากกว่าการใช้ bearer token:

llm.example.com {
	basic_auth {
		apiuser PASTE_BCRYPT_HASH_HERE
	}
	reverse_proxy 127.0.0.1:11434
}

ให้รันคำสั่ง caddy hash-password เพื่อสร้าง bcrypt hash ที่โปรแกรมต้องการ ข้อควรระวังเรื่องชื่อคำสั่งคือ directive นี้เคยใช้ชื่อ basicauth ก่อน Caddy v2.8 และปัจจุบันเปลี่ยนเป็น basic_auth แล้ว ดังนั้นหากคัดลอกการตั้งค่าจากคู่มือเก่ามาใช้ โปรแกรมจะไม่สามารถโหลดการตั้งค่าได้และ Caddy จะแจ้งชื่อ directive ที่ไม่รู้จักให้ทราบ

ไม่ว่าคุณจะเลือกใช้ proxy ตัวใด นี่คือความลับร่วม (shared secret) สำหรับทุกคน Client ทุกตัวที่ถือ token นี้จะมีสิทธิ์เข้าถึงเท่ากันทั้งหมด และการเพิกถอนสิทธิ์หมายถึงคุณต้องแก้ไขไฟล์ config และอัปเดต client ทุกตัวพร้อมกันในคราวเดียว

การป้องกันที่ 3: เกตเวย์ที่ออกคีย์แยกตามไคลเอนต์

เมื่อมีผู้ใช้หรือแอปพลิเคชันมากกว่าหนึ่งรายเรียกใช้งานโมเดล โทเค็นที่ใช้ร่วมกันจะเริ่มไม่เพียงพอ คุณจะไม่สามารถระบุได้ว่าไคลเอนต์รายใดเป็นผู้สร้างภาระงาน และไม่สามารถตัดการเชื่อมต่อเฉพาะรายได้โดยไม่กระทบต่อผู้ใช้ทั้งหมด เกตเวย์จะทำหน้าที่แทนที่พร็อกซี โดยสื่อสารผ่าน API ที่รองรับ OpenAI เหมือนกัน ออกคีย์แยกต่างหากสำหรับแต่ละไคลเอนต์ และบันทึกปริมาณการใช้งานของแต่ละคีย์ เกตเวย์ LiteLLM แบบ self-hosted เป็นคำตอบที่นิยมใช้ ซึ่งช่วยเพิ่มการจัดการงบประมาณต่อคีย์และบันทึกคำขอ (request logs) นอกเหนือไปจากการควบคุมการเข้าถึง

กฎจากการป้องกันที่ 1 ยังคงเดิม Ollama ต้องผูกกับ 127.0.0.1 โดยเกตเวย์ต้องเป็นกระบวนการเดียวที่สื่อสารกับมัน และเกตเวย์ต้องเป็นบริการเดียวที่เปิดรับการเชื่อมต่อจากภายนอก การติดตั้งเกตเวย์บนเครื่องที่ยังเปิดพอร์ต 11434 สู่สาธารณะถือเป็นการป้องกันเพียงในนามเท่านั้น เพราะผู้เรียกใช้งานสามารถข้ามผ่านเกตเวย์ไปได้โดยตรง

กับดักของไฟร์วอลล์: พอร์ตของคอนเทนเนอร์ที่ถูก publish จะข้าม UFW

นี่คือสาเหตุที่ทำให้เกิดอินสแตนซ์ที่เปิดเผยต่อสาธารณะบนเซิร์ฟเวอร์ที่เจ้าของตั้งค่าไฟร์วอลล์ไว้อย่างถูกต้องแล้ว

UFW (uncomplicated firewall) จะเขียนกฎลงใน chain INPUT ของตาราง filter ในเคอร์เนล และ INPUT จะจัดการกับแพ็กเก็ตที่ส่งถึงตัวโฮสต์โดยตรง แต่ flag -p ของ Docker จะเขียนกฎ destination NAT (network address translation) ลงใน chain PREROUTING ของตาราง nat ซึ่งเคอร์เนลจะประเมินกฎนี้ก่อนที่จะตัดสินใจว่าแพ็กเก็ตจะไปที่ใด เมื่อถึงขั้นตอนการตัดสินใจเรื่องเส้นทาง (routing decision) ปลายทางได้ถูกเปลี่ยนเป็นที่อยู่ของคอนเทนเนอร์ไปแล้ว แพ็กเก็ตจึงถูกส่งต่อ (forward) แทนที่จะถูกส่งไปยังเครื่องโฮสต์โดยตรง และมันจะผ่าน 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 ยังคงรายงานว่าไฟร์วอลล์ทำงานอยู่โดยมีนโยบาย default deny ข้อมูลทั้งสองอย่างถูกต้องในเวลาเดียวกัน ซึ่งเป็นเหตุผลว่าทำไมผู้คนถึงเชื่อถือข้อมูลที่ผิด คุณสามารถตรวจสอบกฎที่ทำให้เกิดปัญหานี้ได้ด้วยคำสั่ง:

sudo iptables -t nat -L DOCKER -n

วิธีแก้ไขคือการระบุที่อยู่ (address) ใน 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 เป็นรูปแบบย่อของ -p 0.0.0.0:11434:11434 การระบุ 127.0.0.1 จะเป็นการผูกฝั่งโฮสต์ของการแมปพอร์ตไว้กับ loopback ทำให้ SSH tunnel และ reverse proxy ของคุณยังคงเข้าถึงบริการได้ แต่อินเทอร์เน็ตภายนอกจะไม่สามารถเข้าถึงได้ การสร้างคอนเทนเนอร์ใหม่ในกรณีนี้มีความปลอดภัยเนื่องจากข้อมูลโมเดลถูกเก็บไว้ใน volume ชื่อ ollama ไม่ได้เก็บไว้ภายในตัวคอนเทนเนอร์

ยืนยันว่าข้อมูลทั้งสองมุมมองตรงกัน:

docker port ollama
sudo ss -tlnp | grep 11434

docker port ollama ควรแสดงผลเป็น 11434/tcp -> 127.0.0.1:11434 หากแสดงผลเป็น 0.0.0.0:11434 แสดงว่าคุณยังคงเปิดเผยบริการต่อสาธารณะอยู่ ทำความเข้าใจกลไกนี้เพียงครั้งเดียวและคุณจะสามารถนำไปใช้กับทุกคอนเทนเนอร์ที่คุณ publish: เหตุใดพอร์ตที่ Docker publish จึงข้าม UFW จะอธิบายเกี่ยวกับ chain DOCKER-USER และกฎที่ยังคงอยู่แม้จะรีสตาร์ท Docker หากคุณกำลังสร้างนโยบายความปลอดภัยสำหรับโฮสต์ กฎ UFW ที่ VPS ใหม่จำเป็นต้องมี จะครอบคลุมถึงพื้นฐานที่จำเป็นสำหรับเรื่องนี้

กระบวนการทำงานภายใต้สิทธิ์ของผู้ใช้ใด

สคริปต์ติดตั้งบน Linux จะสร้างบัญชีผู้ใช้เฉพาะขึ้นมาและรันบริการภายใต้บัญชีนั้น:

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

ไฟล์ unit ที่ /etc/systemd/system/ollama.service จะกำหนดค่า User=ollama และ Group=ollama ไว้แล้ว ไม่ควรแก้ไขค่าดังกล่าว การรันคำสั่ง ollama serve ด้วยตนเองในเทอร์มินัลจะทำงานภายใต้สิทธิ์ของผู้ใช้ที่ล็อกอินอยู่ หากผู้ใช้คนนั้นคือ root จะส่งผลให้ API ที่ไม่มีการตรวจสอบสิทธิ์เขียนไฟล์ด้วยสิทธิ์ของ root ให้ตรวจสอบสถานะดังนี้:

ps -o user= -C ollama

ผลลัพธ์ควรเป็น ollama หากเป็นค่าอื่น แสดงว่ามีกระบวนการที่รันด้วยตนเองทำงานควบคู่ไปกับหรือแทนที่ unit หลัก แนวคิดเดียวกันนี้ใช้ได้กับ daemon ทุกตัวที่คุณเพิ่มในภายหลัง และเนื้อหาใน การรันบริการด้วยสิทธิ์ของผู้ใช้ที่มีข้อจำกัด จะอธิบายรายละเอียดเรื่องนี้ไว้อย่างครบถ้วน

วิธีตรวจสอบความปลอดภัยของ Ollama API endpoint

ไม่ว่าคุณจะเลือกวิธีใด การทดสอบหนึ่งอย่างจะช่วยยืนยันได้ โดยต้องรันจากเครื่องอื่น:

curl -m 5 http://YOUR_SERVER_IP:11434/api/version
curl -m 5 http://YOUR_SERVER_IP:11434/api/tags

ทั้งสองคำสั่งควรจะ time out หรือถูกปฏิเสธการเชื่อมต่อ หากคุณสร้าง proxy ขึ้นมา ทั้งสอง path เดียวกันบน hostname ของ proxy ควรส่งค่า 401 กลับมาหากไม่มีข้อมูลยืนยันตัวตน และส่ง JSON จริงกลับมาหากมีข้อมูลยืนยันตัวตนที่ถูกต้อง

จากนั้นให้อ่าน access log หนึ่งครั้ง เพราะ log จะบอกคุณว่ามีใครพบพอร์ตนี้ในขณะที่มันยังเปิดอยู่หรือไม่:

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

Ollama จะเขียน log หนึ่งบรรทัดต่อหนึ่งคำขอและรวมถึงที่อยู่ของ client ไว้ด้วย:

[GIN] 2026/08/12 - 14:01:10 | 200 | 103.965898ms | 127.0.0.1 | POST "/api/generate"

ทุกบรรทัดควรแสดงค่า 127.0.0.1 เมื่อ Ollama ถูกผูกไว้กับ loopback แล้ว เพราะนั่นเป็นที่อยู่เดียวที่การเชื่อมต่อจะส่งเข้ามาได้ หากมีที่อยู่สาธารณะปรากฏในคอลัมน์นั้น แสดงว่าเป็นคำขอจากภายนอก และ timestamp จะบอกคุณว่าเกิดขึ้นเมื่อใด ผลลัพธ์ที่คุณต้องการคือไม่ควรมี output ใดๆ ออกมาจากคำสั่งนั้น หากเรื่องโมเดลเป็นสิ่งใหม่สำหรับคุณ การรัน Ollama บน VPS จะครอบคลุมเนื้อหาเรื่องการติดตั้ง การเลือกขนาดโมเดล และขีดจำกัดของหน่วยความจำที่จะเป็นตัวตัดสินว่าโมเดลใดจะโหลดขึ้นมาใช้งานได้จริง

FAQ

Ollama มี API key หรือรหัสผ่านหรือไม่

ไม่มี เซิร์ฟเวอร์ที่คุณรันไม่มีระบบยืนยันตัวตนใดๆ และเอกสารอย่างเป็นทางการระบุว่าไม่จำเป็นต้องใช้การยืนยันตัวตนเพื่อเข้าถึง API สิ่งที่เรียกว่า "Ollama API key" ทั้งสองอย่างนั้นมีจุดประสงค์ในทางตรงกันข้าม คู่กุญแจ Ed25519 ใน /usr/share/ollama/.ollama/ ใช้เพื่อยืนยันตัวตนเครื่องของคุณกับ ollama.com เพื่อให้คุณสามารถ push โมเดลและ pull โมเดลส่วนตัวได้ ส่วน OLLAMA_API_KEY คือข้อมูลประจำตัวที่ไคลเอนต์ของคุณส่งไปยัง API ที่โฮสต์ไว้ที่ https://ollama.com/api ตัว ollama serve ของคุณเองไม่ได้อ่านทั้งสองอย่างนี้ ดังนั้นการควบคุมการเข้าถึงจึงต้องทำผ่านเครือข่ายหรือผ่านพร็อกซีที่วางไว้ด้านหน้า

OLLAMA_HOST=0.0.0.0 ปลอดภัยหรือไม่หากฉันมีไฟร์วอลล์

ปลอดภัยก็ต่อเมื่อไม่มีสิ่งอื่นใดมาเขียนกฎไฟร์วอลล์บนเครื่องนั้น 0.0.0.0 หมายความว่า listener นั้นมีอยู่จริงบนอินเทอร์เฟซสาธารณะ และคุณกำลังเชื่อใจเพียงแค่ไฟร์วอลล์ว่าจะป้องกันไม่ให้เข้าถึงได้ ความเชื่อใจนี้จะพังทลายทันทีที่ Docker เปิดพอร์ต เพราะกฎ DNAT ที่ Docker เพิ่มเข้าไปในตาราง nat จะถูกประเมินก่อนที่แพ็กเก็ตจะไปถึงเชน INPUT ที่ UFW ทำงานอยู่ ทำให้แพ็กเก็ตถูกส่งต่อไปโดยที่ UFW ไม่เห็น การผูก (bind) ไว้กับ 127.0.0.1 หรือที่อยู่ของไพรเวททันเนลจะช่วยเอา listener ออกจากอินเทอร์เฟซสาธารณะ ดังนั้นหากเกิดความผิดพลาดที่ไฟร์วอลล์ ก็จะไม่มีสิ่งใดหลุดรอดออกไปได้

ฉันจะตรวจสอบได้อย่างไรว่าพอร์ต Ollama ของฉันเปิดสู่สาธารณะหรือไม่

ให้รัน sudo ss -tlnp | grep 11434 บนเซิร์ฟเวอร์ และรัน curl -m 5 http://YOUR_SERVER_IP:11434/api/version จากเครื่องอื่น ผลลัพธ์ที่คุณต้องการคือ ss แสดง 127.0.0.1:11434 และการรัน curl จากระยะไกลหมดเวลา (timeout) หาก ss แสดง 0.0.0.0:11434 หรือ *:11434 ในขณะที่การรัน curl จากระยะไกลส่งค่า JSON กลับมา นั่นหมายความว่า API ทั้งหมดสามารถเข้าถึงได้จากภายนอก ห้ามทดสอบด้วย curl บนตัวเซิร์ฟเวอร์เอง เพราะ loopback จะตอบรับเสมอไม่ว่า bind address จะเป็นอะไรก็ตาม

ฉันสามารถย้ายพอร์ตจาก 11434 ไปเป็นพอร์ตอื่นแบบสุ่มได้หรือไม่

ไม่ได้ และเหตุผลนั้นสำคัญมาก การเปลี่ยนพอร์ตไม่ได้ช่วยชะลอสิ่งใดนอกจากแค่การสแกนพอร์ตเดียวเท่านั้น เครื่องมือสแกนจะไล่ตรวจทั้งช่วงพอร์ต และการส่งคำขอเพียงครั้งเดียวไปยัง /api/tags ก็สามารถระบุบริการได้ไม่ว่าจะมาทางพอร์ตใดก็ตาม การย้ายพอร์ตยังทำให้ค่าเริ่มต้นของไคลเอนต์ทุกตัวใช้งานไม่ได้และทำให้การตั้งค่าของคุณเองเข้าใจยากขึ้นในภายหลัง ให้เปลี่ยนไป bind กับ loopback แทน ซึ่งเป็นการเอา listener ออกไปเลยดีกว่าการย้ายตำแหน่ง

มีคนเข้าถึง Ollama ที่เปิดไว้ของฉันได้ ฉันควรตรวจสอบอะไรบ้าง

ให้ bind ไปที่ 127.0.0.1 แล้วรีสตาร์ทเซอร์วิสก่อน เพื่อหยุดการเปิดเผยข้อมูลก่อนที่คุณจะเริ่มตรวจสอบ จากนั้นให้รัน journalctl -u ollama --since "-30 days" | grep GIN | grep -v 127.0.0.1 เพื่อดูว่าที่อยู่ภายนอกใดเรียกใช้ endpoint ใดบ้างและเมื่อใด เปรียบเทียบ ollama list กับโมเดลที่คุณตั้งใจจะมีไว้ เนื่องจาก /api/pull ไม่มีการยืนยันตัวตน และโมเดลที่คุณไม่ได้ pull มานั้นถือเป็นทั้งการใช้พื้นที่ดิสก์และเป็นหลักฐาน ตรวจสอบพื้นที่ว่างด้วย df -h Ollama ไม่ได้บันทึกข้อความ prompt ไว้ในระดับ log ปกติ ดังนั้นคุณจะมีบันทึกว่าใครเป็นคนเรียกใช้และเรียกใช้โมเดลใด แต่จะไม่มีบันทึกว่ามีการสร้างเนื้อหาอะไรออกมาบ้าง