ในบทความก่อน เราเล่าว่าทำไม ntfy ถึงคุ้มค่าจะ host เอง — ฟรี, ยิงไม่จำกัด, คุมข้อมูลเอง, เป็น service กลางให้หลาย project ใช้ร่วมกันบน VPS $5 ตัวเดียว
บทความนี้ลงมือจริง — วาง ntfy บน VPS ตั้งแต่ต้นจนใช้งานได้ พร้อม gotcha ที่คู่มือไม่ค่อยบอก (โดยเฉพาะเรื่อง nginx buffering กับ push บนมือถือที่หลายคนติด)
_หมายเหตุ: IP/โดเมน/token ในบทความเป็นตัวอย่าง — ของจริงเก็บในไฟล์ที่ไม่ commit_
ภาพรวมสถาปัตยกรรม
หลักคิด 3 ข้อ:
1. ntfy bind แค่ `127.0.0.1` — ไม่ expose ออกเน็ตตรง ๆ
2. nginx เป็นด่าน TLS เดียว — จัดการ HTTPS + reverse proxy เข้า container
3. Cloudflare หน้าสุด — DNS proxied ได้ WAF/DDoS ฟรี
Step 1 — docker-compose
ntfy มี official image (`binwiederhier/ntfy`) รันเป็น container ตัวเดียว วางไว้ที่ `/apps/ntfy/`:
จุดสำคัญ: `127.0.0.1:2586:80` — bind แค่ localhost เข้าจากภายนอกไม่ได้ ต้องผ่าน nginx
💬 ไม่ต้องพิมพ์ YAML เอง — บอก AI ได้เลย เช่น _"สร้าง docker-compose สำหรับ ntfy ให้ bind แค่ 127.0.0.1:2586 มี volume เก็บ auth/cache และ healthcheck"_ แล้ว AI (Claude Code / ChatGPT) ร่างให้ครบ เราแค่ตรวจว่า bind localhost จริงไหม
Step 2 — config (server.yml)
สองบรรทัดที่ทำหรือพังทั้งระบบ:
- `auth-default-access: deny-all` — ค่า default คือใครก็ยิงได้ ต้องปิดเป็น deny-all ให้ทุก publish/subscribe ต้อง authenticate ไม่งั้น topic ของเราใครก็เขียน/อ่านได้
- `upstream-base-url: https://ntfy.sh` — self-host ไม่ push background เองบนมือถือ (iOS/Android ต้องผ่าน FCM/APNs ที่ผูกกับแอป ntfy ตัวจริง) → ต้องให้ ntfy.sh เป็น relay ปลุก push ให้ (ตัวข้อความยังอยู่ server เรา ntfy.sh แค่เคาะประตู)
Step 3 — nginx (จุดที่พลาดกันเยอะ)
ntfy ส่งข้อความแบบ real-time ผ่าน SSE/WebSocket — nginx config ปกติจะ buffer ทำให้แจ้งเตือนดีเลย์/ไม่มา ต้องตั้งพิเศษ:
💬 nginx config จำยาก — ให้ AI จัดการ บอกว่า _"ทำ nginx reverse proxy ให้ ntfy ที่ 127.0.0.1:2586 รองรับ WebSocket และปิด buffering"_ แล้ว AI จะใส่ 2 gotcha (`Connection "upgrade"` + `proxy_buffering off`) ให้เอง — เราแค่รู้ว่า ต้องมี 2 อันนี้ ไว้ตรวจ
Step 4 — TLS ด้วย Let's Encrypt
certbot เติม block `443` + redirect `80→443` ให้อัตโนมัติ + ตั้ง auto-renew — จบใน command เดียว
โมเดลความปลอดภัย (คิดก่อน ไม่ใช่แปะทีหลัง)
เพิ่ม project ใหม่ = 3 บรรทัด ไม่ต้อง restart:
★ กฎเหล็ก: ห้ามใส่ข้อมูลลับในตัวแจ้งเตือน
แจ้งเตือนเป็น plaintext บน server (cache ≤12h) และ Cloudflare เห็น traffic — ไม่มี end-to-end encryption → ห้ามใส่ PII / ราคา / รหัส ในตัว `message`
ส่งแบบ generic + `click` ลิงก์กลับเข้าแอปที่ auth หลัง login:
แม้ notification รั่ว ก็ไม่มีข้อมูลจริงหลุด — ข้อมูล sensitive อยู่หลัง login เสมอ
Push เจาะจงรายคน (targeted push)
ntfy เป็น topic-based ไม่มี concept "ส่งหา user X" ตรง ๆ — เทคนิคคือ 1 คน = 1 topic ส่วนตัวที่เดาไม่ได้:
topic ส่วนตัวสุ่ม 96-bit (เดาไม่ได้) แล้วอยู่ใต้ namespace `chipwood_*` เดิม → token เดิม publish ได้ทันที ไม่ต้องเพิ่มสิทธิ์:
เก็บ `ntfyTopic` ไว้ใน record ของ user → เวลางานถูก assign ก็ push เข้า topic ส่วนตัวคนนั้น เด้งเฉพาะเขาคนเดียว
💬 บอก AI ให้เขียนให้ — _"เพิ่มฟิลด์ ntfyTopic ต่อ user สุ่ม 96-bit ตอนสร้างบัญชี แล้วเขียน pushToUser(userId) ที่ push เข้า topic ส่วนตัวคนนั้น"_ — เราแค่ย้ำหลักการ (สุ่มจริง ห้ามเดาได้) ที่เหลือให้ AI ลงมือ
ความปลอดภัยของ targeted push อยู่ที่ topic เดาไม่ได้จริง — ห้ามใช้ค่าที่เดาได้ (เช่น employeeId) ต้องสุ่ม entropy สูง
Helper ฝั่งแอป (ไม่ให้ล้ม flow หลัก)
wrapper ควร ไม่ throw — แจ้งเตือนพังไม่ควรทำให้บันทึกงานหลักพัง:
Operations & Troubleshooting
อย่าลืม backup: `user.db` (auth — ถ้าหาย token/ACL หายหมด) + `cache.db` + ไฟล์ credential
✅ Checklist: สั่ง AI วางระบบ ntfy ต้องบอกอะไรบ้าง (7 ข้อ)
ยุคนี้ไม่ต้องจำ syntax ทุกบรรทัด — ให้ AI (Claude Code / ChatGPT) ลงมือแทนได้ แต่ เราต้องรู้ว่าต้องสั่งอะไร ไม่งั้น AI ก็ตั้งค่าไม่ครบ นี่คือ 7 เรื่องที่ควรระบุให้ชัดในคำสั่ง:
1. โดเมน + bind localhost — ระบุ subdomain (เช่น `notify.บริษัท.com`) และย้ำให้ container bind แค่ `127.0.0.1` ให้ nginx เป็นด่านเดียว (อย่า expose port ตรง)
2. ปิด public ด้วย `deny-all` — สั่งให้ `auth-default-access: deny-all` + สร้าง token ต่อ project ไม่เปิด anonymous (กัน topic เราโดนใครก็เขียน/อ่าน)
3. แยก namespace ต่อ project — บอก prefix ของแต่ละงาน (`chipwood_`, `autocar_`) ให้ตั้ง ACL แยกสิทธิ์ ยิงข้าม project ไม่ได้
4. push ขึ้นจอมือถือ (`upstream-base-url`) — ย้ำว่าต้องเด้งเข้ามือถือจริง → AI จะใส่ `upstream-base-url: https://ntfy.sh` ให้ (ไม่งั้นเห็นในแอปแต่ไม่ push background)
5. nginx รองรับ SSE/WebSocket — สั่งให้ตั้ง `Connection "upgrade"` + `proxy_buffering off` (2 gotcha ที่ทำคนติดบ่อยสุด)
6. กฎห้ามใส่ข้อมูลลับ — บอก AI ว่าตัว `message` ต้อง generic + ใส่ `click` ลิงก์กลับแอป ห้ามใส่ PII/ราคา/รหัส (แจ้งเตือนเป็น plaintext บน server)
7. helper ไม่ throw + วาง backup — ให้ wrapper คืน `false` เมื่อ error (ไม่ล้ม flow หลัก) และเตือนให้ใส่ `user.db` เข้า backup routine
เก็บ 7 ข้อนี้ไว้เป็น "แบบฟอร์มสั่งงาน AI" — วางลงในคำสั่งเดียว AI ก็ setup ให้ครบ ปลอดภัย และไม่ตกหล่นเรื่องความปลอดภัย
สรุป
self-host ntfy ไม่ใช่งานใหญ่ — Docker container ตัวเดียว + nginx block + certbot 1 คำสั่ง แต่รายละเอียดที่ทำหรือพังอยู่ที่ 3 จุด: `deny-all` (ปิด public), `upstream-base-url` (ปลุก push มือถือ), และ nginx `Connection "upgrade"` + `proxy_buffering off` (SSE/WebSocket)
พอวางเสร็จ เราได้ระบบแจ้งเตือนกลางที่ทุก project ยิงเข้าตัวเดียว แยกสิทธิ์ต่อ project ปลอดภัยด้วย token + namespace และคุมข้อมูลเอง 100% — ทั้งหมดบน VPS ที่มีอยู่แล้ว ต้นทุนส่วนเพิ่ม ≈ 0
สนใจวางระบบแจ้งเตือนกลาง / เชื่อม notification เข้าระบบหลังบ้านของธุรกิจ — ทักมาคุยได้ครับ