SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีติดตั้ง CalDAV Server บน VPS ด้วย Radicale

สร้างเซิร์ฟเวอร์ปฏิทินส่วนตัวบน VPS เพื่อซิงค์ข้อมูลข้ามอุปกรณ์โดยไม่ต้องพึ่ง Google เรียนรู้วิธีตั้งค่า Radicale พร้อมระบบ TLS และการทำ Service Discovery อย่างละเอียด

สิ่งที่คุณกำลังสร้าง

ปฏิทินแบบ self-hosted คือเซิร์ฟเวอร์ CalDAV หนึ่งตัวบน VPS ที่คุณควบคุม โดยอยู่หลัง TLS และมีการเข้าสู่ระบบแยกรายบุคคล โทรศัพท์ในกระเป๋าและแล็ปท็อปบนโต๊ะของคุณจะแสดงกิจกรรมเดียวกัน รวมถึงแล็ปท็อปของคู่ของคุณด้วย โดยไม่มีบัญชี Google เข้ามาเกี่ยวข้อง

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

การติดตั้งมีขนาดเล็ก Radicale เป็นแพ็กเกจ Python หนึ่งตัวและมีค่าคอนฟิกประมาณสิบบรรทัด สิ่งที่จะตัดสินว่าระบบจะใช้งานได้ยาวนานเกินเดือนแรกหรือไม่ คือ TLS, การค้นหาบริการ (discovery), การจัดการคอลเลกชันรายผู้ใช้ และการสำรองข้อมูล ซึ่งเนื้อหาส่วนใหญ่ด้านล่างจะเน้นไปที่หัวข้อเหล่านี้

CalDAV คืออะไร และทำไมจึงสำคัญ?

CalDAV คือการซิงค์ปฏิทินผ่านโปรโตคอล HTTP โดยถูกกำหนดไว้ใน RFC 4791 ในฐานะส่วนขยายของ WebDAV (Web Distributed Authoring and Versioning ซึ่งเป็นชุดคำสั่ง HTTP เพิ่มเติมที่กำหนดไว้ใน RFC 4918) ปฏิทินเปรียบเสมือนคอลเลกชันที่มีพฤติกรรมคล้ายกับไดเรกทอรี โดยเหตุการณ์หนึ่งรายการคือไฟล์หนึ่งไฟล์ที่อยู่ภายใน ซึ่งเขียนด้วยรูปแบบข้อความ iCalendar (RFC 5545) ซึ่งเป็นรูปแบบเดียวกับ .ics ที่แนบมาในอีเมลของคุณ

ไคลเอนต์จะใช้ HTTP ปกติร่วมกับคำสั่งที่เพิ่มเข้ามาบางส่วน PROPFIND ใช้เพื่อสอบถามว่ามีข้อมูลอะไรอยู่ที่นี่และมีคุณสมบัติอย่างไร REPORT ใช้เพื่อขอข้อมูลเฉพาะส่วนที่กรองแล้ว เช่น เหตุการณ์ทั้งหมดในช่วงวันที่ที่กำหนด PUT ใช้สำหรับเขียนเหตุการณ์ลงไปหนึ่งรายการ และ DELETE ใช้สำหรับลบเหตุการณ์นั้นออก เหตุการณ์ทุกรายการจะมีบรรทัด UID กำกับไว้ ซึ่งตัวระบุนี้เองที่ทำให้สองอุปกรณ์ตกลงกันได้ว่ากำลังดูเหตุการณ์เดียวกันอยู่ ไม่ใช่แค่สำเนาของข้อมูล

ความสามารถในการย้ายข้อมูล (Portability) คือผลตอบแทนที่ได้รับ และเป็นเหตุผลทั้งหมดที่ควรให้ความสำคัญกับเรื่องนี้ ทั้ง iOS, macOS, Thunderbird, Evolution และ Android ผ่านทาง DAVx⁵ ต่างก็รองรับ CalDAV ข้อมูลของคุณจึงไม่ได้ถูกผูกติดอยู่กับเซิร์ฟเวอร์ที่คุณเลือกใช้ในวันนี้ คุณสามารถย้ายไฟล์ไปยังเซิร์ฟเวอร์ CalDAV อื่น แล้วชี้ไคลเอนต์ไปยังชื่อโฮสต์ใหม่ได้ทันทีโดยไม่ต้องเปลี่ยนแปลงสิ่งอื่นใด

CardDAV ก็ทำงานในลักษณะเดียวกัน โดยเป็นแนวคิดเดียวกันสำหรับรายชื่อผู้ติดต่อ ซึ่งกำหนดไว้ใน RFC 6352 และจัดเก็บไฟล์ vCard แทนที่จะเป็นเหตุการณ์ เซิร์ฟเวอร์ทุกตัวที่ระบุไว้ด้านล่างให้บริการทั้งสองโปรโตคอลจากบัญชีเดียวกัน ดังนั้นเมื่อคุณตั้งค่าปฏิทินสำเร็จแล้ว การใช้งานสมุดรายชื่อก็เป็นเพียงการทำเครื่องหมายในช่องเลือกเท่านั้น

คุณควรเลือกใช้ CalDAV server ตัวใด?

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

Baikal เป็นตัวเลือกที่มีหน้าเว็บสำหรับจัดการ (admin panel) ทำงานบน PHP และไลบรารี sabre/dav โดยเก็บข้อมูลผู้ใช้และปฏิทินไว้ใน SQLite หรือ MySQL ช่วยให้คุณสามารถเพิ่มผู้ใช้งานผ่านเบราว์เซอร์ได้โดยไม่ต้องใช้ command line เลือกใช้ตัวนี้หากมีการเพิ่มหรือลบบัญชีผู้ใช้อยู่บ่อยครั้ง

Nextcloud เหมาะสมเมื่อปฏิทินเป็นเพียงฟีเจอร์หนึ่งในหลายๆ อย่างที่คุณต้องการ คุณจะได้รับทั้งปฏิทิน รายชื่อผู้ติดต่อ ไฟล์ และแอปบนมือถือ แต่ต้องแลกมาด้วยการติดตั้ง PHP-FPM, ฐานข้อมูล และตัวรัน background job หากคุณรู้สึกว่านั่นเป็นภาระหนักเกินความจำเป็น ทางเลือกอื่นที่เบากว่า Nextcloud จะช่วยให้คุณเห็นภาพรวมของตัวเลือกอื่น และ การซิงค์ไฟล์ด้วยตนเอง จะครอบคลุมอีกครึ่งหนึ่งของเหตุผลที่ผู้คนเลือกติดตั้ง Nextcloud

DAViCal เป็นตัวเลือกที่ใช้ PostgreSQL มาอย่างยาวนาน ควรพิจารณาตัวเลือกนี้ก็ต่อเมื่อคุณใช้งาน PostgreSQL อยู่แล้วและต้องการเก็บข้อมูลปฏิทินไว้ในฐานข้อมูลดังกล่าวเท่านั้น

การติดตั้ง Radicale บน Ubuntu 24.04

Radicale 3.5.10 เป็นรุ่นล่าสุด ณ เดือนสิงหาคม 2026 ให้ติดตั้งลงใน virtual environment ของตนเอง

sudo apt update
sudo apt install -y python3-venv apache2-utils nginx
sudo useradd --system --user-group --home-dir / --shell /usr/sbin/nologin radicale
sudo install -d -o radicale -g radicale -m 750 /var/lib/radicale/collections
sudo install -d -m 750 -o root -g radicale /etc/radicale
sudo python3 -m venv /opt/radicale/venv
sudo /opt/radicale/venv/bin/pip install --upgrade radicale

การใช้ virtual environment ไม่ใช่แค่เรื่องของรูปแบบ แต่การใช้ sudo pip install radicale กับ Python ของระบบจะหยุดทำงานด้วย error: externally-managed-environment เนื่องจาก Ubuntu กำหนดให้ Python ของระบบอยู่ภายใต้การจัดการของ apt เพื่อป้องกันไม่ให้ pip เขียนทับไฟล์ที่ติดตั้งผ่านแพ็กเกจ

เขียน /etc/radicale/config:

[server]
hosts = 127.0.0.1:5232

[auth]
type = htpasswd
htpasswd_filename = /etc/radicale/users
htpasswd_encryption = autodetect

[storage]
filesystem_folder = /var/lib/radicale/collections

hosts ถูกตั้งค่าให้ผูกกับ loopback โดยเจตนา โดยมี nginx ทำหน้าที่จัดการ TLS และส่งต่อคำขอไปยังพอร์ตดังกล่าว ดังนั้น Radicale จึงไม่จำเป็นต้องเชื่อมต่อกับอินเทอร์เน็ตโดยตรง ตัวอย่าง upstream ของ 0.0.0.0:5232 จะเผยแพร่บริการที่ไม่มีการเข้ารหัสซึ่งยอมรับรหัสผ่าน ซึ่งเป็นข้อผิดพลาดสำคัญที่ต้องหลีกเลี่ยงในกรณีนี้

ถัดไปคือการจัดการบัญชีผู้ใช้ -5 จะเลือกใช้ SHA-512 crypt ซึ่ง Radicale สามารถอ่านได้ด้วย htpasswd_encryption = autodetect โดยไม่ต้องติดตั้งโมดูลเพิ่มเติม:

sudo htpasswd -5 -c /etc/radicale/users you
sudo htpasswd -5 /etc/radicale/users partner
sudo chown root:radicale /etc/radicale/users
sudo chmod 640 /etc/radicale/users

-c จะสร้างไฟล์ใหม่และลบข้อมูลเดิมทิ้งทั้งหมด ให้ใช้คำสั่งนี้สำหรับผู้ใช้คนแรกเท่านั้น การรัน htpasswd -5 -c อีกครั้งในภายหลังจะลบบัญชีทั้งหมดที่เพิ่มเข้ามาหลังจากคนแรกทิ้งไป ซึ่งจะส่งผลให้ผู้ใช้คนแรกซิงค์ข้อมูลได้ปกติ ในขณะที่คนอื่นจะติดอยู่ที่หน้าต่างแจ้งเตือนรหัสผ่านไม่สิ้นสุด นอกจากนี้ยังสามารถใช้ Bcrypt ได้เช่นกัน แต่จำเป็นต้องติดตั้งเพิ่มด้วย radicale[bcrypt]

สร้าง /etc/systemd/system/radicale.service โดยปรับเปลี่ยนจาก unit ในเอกสารประกอบของ Radicale:

[Unit]
Description=CalDAV and CardDAV server
After=network.target
Requires=network.target

[Service]
ExecStart=/opt/radicale/venv/bin/python -m radicale
Restart=on-failure
User=radicale
UMask=0027
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
NoNewPrivileges=true
ReadWritePaths=/var/lib/radicale/

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now radicale
curl -i http://127.0.0.1:5232/

ผลลัพธ์ที่ถูกต้องคือ 401 Unauthorized พร้อมส่วนหัว WWW-Authenticate: บริการกำลังทำงานและเปิดใช้งานการยืนยันตัวตนแล้ว Connection refused หมายความว่าบริการไม่เคยเริ่มทำงาน และ journalctl -u radicale -n 50 จะระบุตัวเลือกที่ถูกปฏิเสธ ProtectSystem=strict จะ mount ระบบไฟล์แบบอ่านได้อย่างเดียวสำหรับบริการนี้ ดังนั้น ReadWritePaths=/var/lib/radicale/ จึงเป็นบรรทัดที่อนุญาตให้บันทึกข้อมูลเหตุการณ์ได้ หากลบบรรทัดนี้ออก การอ่านข้อมูลจะยังคงทำงานได้ปกติ แต่การเขียนข้อมูลทั้งหมดจะล้มเหลว

TLS ไม่ใช่ทางเลือก เพราะไคลเอนต์ปฏิเสธการเชื่อมต่อแบบ plaintext

CalDAV ใช้วิธีการยืนยันตัวตนแบบ HTTP Basic ซึ่งจะส่ง user:password ที่เข้ารหัสด้วย base64 ในทุกคำขอ Base64 เป็นเพียงการเข้ารหัส ไม่ใช่การเข้ารหัสลับ (encryption) หากใช้งานผ่าน HTTP ปกติ คุณกำลังส่งรหัสผ่านให้กับทุกเครือข่ายระหว่างโทรศัพท์และเซิร์ฟเวอร์ตลอดเวลาที่มีการซิงค์

ไคลเอนต์จะบังคับใช้ข้อกำหนดนี้แทนคุณ เอกสารของ Radicale ระบุว่า macOS Calendar.app อาจปฏิเสธที่จะส่งข้อมูลยืนยันตัวตนผ่าน HTTP ที่ไม่ปลอดภัยโดยไม่แจ้งเตือน และ iOS ก็มีพฤติกรรมเช่นเดียวกัน บัญชีจะดูเหมือนถูกตั้งค่าไว้แล้วแต่จะไม่ซิงค์ข้อมูลใดๆ โดยไม่มีข้อความแสดงข้อผิดพลาดให้ตรวจสอบ

ให้ชี้ A record ของ cal.example.com ไปที่ VPS ก่อน เนื่องจากผู้ออกใบรับรอง (certificate authority) จะทำการตรวจสอบ จากนั้นสร้าง /etc/nginx/sites-available/cal.example.com:

server {
    listen 80;
    server_name cal.example.com;

    location / {
        proxy_pass        http://localhost:5232/;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header  X-Forwarded-Proto $scheme;
        proxy_set_header  Host $http_host;
        proxy_pass_header Authorization;
    }

    location = /.well-known/caldav  { return 301 https://$host/; }
    location = /.well-known/carddav { return 301 https://$host/; }
}

บรรทัด proxy header ทั้งสี่บรรทัดมาจากเอกสารของ Radicale ให้คงค่าไว้ตามนั้น

sudo ln -s /etc/nginx/sites-available/cal.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d cal.example.com
curl -i -u you https://cal.example.com/

nginx -t จะแสดงผล syntax is ok และ test is successful ให้ทำการ reload หลังจากที่คำสั่งทำงานสำเร็จเท่านั้น เนื่องจากการ reload ด้วยไฟล์ที่มีข้อผิดพลาดจะทำให้ config เดิมยังคงทำงานอยู่และบดบังข้อผิดพลาดจนกว่าจะมีการ restart ครั้งถัดไป Certbot จะแก้ไขไฟล์ site โดยตรง โดยจะติดตั้งใบรับรอง สลับบล็อกไปที่พอร์ต 443 และเพิ่มการ redirect จากพอร์ต 80 คำสั่ง curl สุดท้ายจะถามรหัสผ่านและควรส่งคืนค่า 200 ซึ่งเป็นหน้าเว็บอินเทอร์เฟซของ Radicale เอง หากได้รับ 502 Bad Gateway หมายความว่า nginx กำลังทำงานอยู่ แต่ Radicale ไม่ได้ฟังคำสั่งที่พอร์ต 5232

เหตุใดการเพิ่มบัญชีบนโทรศัพท์จึงล้มเหลว?

เนื่องจากกระบวนการค้นหา (discovery) RFC 6764 อธิบายวิธีที่ไคลเอนต์เปลี่ยนชื่อโฮสต์ให้เป็น URL ของปฏิทิน โดยไคลเอนต์จะมองหา _caldavs._tcp SRV record จากนั้นจะร้องขอ https://cal.example.com/.well-known/caldav และคาดหวังว่าจะได้รับการเปลี่ยนเส้นทาง (redirect) ไปยัง DAV root จากจุดนั้นไคลเอนต์จะร้องขอ current-user-principal ตามด้วย calendar-home-set ของ principal นั้น และหลังจากนั้นจึงจะมองเห็นปฏิทินของคุณ โทรศัพท์ให้ช่องกรอกข้อมูลเซิร์ฟเวอร์เพียงช่องเดียว ดังนั้นทุกขั้นตอนจึงต้องทำงานได้โดยอัตโนมัติ

curl -sI https://cal.example.com/.well-known/caldav

คำตอบที่ถูกต้องคือ HTTP/2 301 พร้อมกับส่วนหัว location: https://cal.example.com/ การที่ได้รับ 404 ในส่วนนั้นเป็นสาเหตุที่ทำให้ iOS แจ้งว่าไม่สามารถตรวจสอบข้อมูลบัญชีได้ ในขณะที่ Thunderbird บนเครือข่ายเดียวกันกลับใช้งานได้ เนื่องจาก Thunderbird ใช้ URL เต็มที่คุณพิมพ์ลงไป จึงไม่จำเป็นต้องอาศัยการเปลี่ยนเส้นทาง

เป้าหมายของการเปลี่ยนเส้นทางขึ้นอยู่กับเซิร์ฟเวอร์แต่ละตัว Radicale ที่ให้บริการที่ root ของเว็บไซต์จะเปลี่ยนเส้นทางไปยัง / ส่วน Baikal มีตัวอย่างกฎที่เปลี่ยนเส้นทางไปยัง /dav.php ด้วยสถานะ 308 และ Nextcloud จะเปลี่ยนเส้นทางไปยัง /remote.php/dav/

สร้างปฏิทินและแชร์กับคู่ของคุณ

ไคลเอนต์จำนวนมากไม่สามารถสร้างปฏิทินได้ ทำได้เพียงแค่สมัครสมาชิก (subscribe) เท่านั้น ให้เปิด https://cal.example.com/ ในเบราว์เซอร์ ล็อกอินด้วย you แล้วสร้างปฏิทินที่นั่น บนดิสก์ปฏิทินจะถูกจัดเก็บไว้ภายใต้ /var/lib/radicale/collections/collection-root/you/ โดยมีตัวระบุที่ถูกสร้างขึ้นอัตโนมัติเป็นชื่อโฟลเดอร์

ระบบจัดการสิทธิ์เริ่มต้นของ Radicale คือ owner_only ซึ่งหมายความว่าบัญชีที่ผ่านการตรวจสอบสิทธิ์แล้วจะสามารถอ่านและเขียนคอลเลกชันของตนเองได้ภายใต้ /USERNAME/ เท่านั้น สำหรับครัวเรือนส่วนใหญ่ นี่คือการตั้งค่าที่เหมาะสมที่สุด และวิธีที่ง่ายที่สุดในการแชร์ปฏิทินคือการใช้บัญชีที่สาม ให้สร้าง household ด้วย htpasswd สร้างปฏิทินที่แชร์ไว้ภายใต้การล็อกอินนั้น แล้วเพิ่มปฏิทินดังกล่าวในแต่ละอุปกรณ์ในฐานะบัญชี CalDAV บัญชีที่สอง วิธีนี้ใช้งานได้กับทุกไคลเอนต์รวมถึง iOS เพราะปฏิทินนั้นตั้งอยู่ในโฮมไดเรกทอรีของบัญชีนั้นโดยตรง

หากคุณต้องการการควบคุมที่ละเอียดขึ้น ให้เปลี่ยนไปใช้การกำหนดสิทธิ์แบบอิงตามกฎ (rule-based rights) โดยเพิ่มข้อความนี้ลงใน /etc/radicale/config:

[rights]
type = from_file
file = /etc/radicale/rights

จากนั้นให้ตั้งค่า /etc/radicale/rights โดยอ้างอิงจากตัวอย่างในเอกสารประกอบของ Radicale:

[root]
user: .+
collection:
permissions: R

[principal]
user: .+
collection: {user}
permissions: RW

[own-calendars]
user: .+
collection: {user}/[^/]+
permissions: rw

[shared-household]
user: you|partner
collection: you/2f0a9c1e-1f4c-4c2b-9a1b-0d2f7a5c9e11
permissions: rw

ตัวอักษรพิมพ์ใหญ่และพิมพ์เล็กมีความหมายต่างกัน R และ W ใช้สำหรับอ่านและเขียนคอลเลกชันที่ไม่ใช่ปฏิทินหรือสมุดรายชื่อ ซึ่งเป็นสิ่งที่โฟลเดอร์หลัก (principal folder) เป็นอยู่ ส่วน r และ w ใช้สำหรับอ่านและเขียนตัวปฏิทินโดยตรง ให้แทนที่ตัวระบุนั้นด้วยชื่อโฟลเดอร์จริงของปฏิทินของคุณจากพาธการจัดเก็บข้อมูลที่ระบุไว้ข้างต้น

ข้อจำกัดที่ต้องทราบคือ ไคลเอนต์ที่อ่านเฉพาะโฮมไดเรกทอรีของปฏิทินจะไม่แสดงปฏิทินที่อยู่ในพาธของผู้ใช้อื่น เนื่องจากกระบวนการค้นหา (discovery) จะไม่เข้าไปถึงที่นั่น Thunderbird และ DAVx⁵ สามารถเพิ่มปฏิทินดังกล่าวได้โดยใช้ URL เต็ม แต่ iOS ไม่สามารถทำได้ นี่คือเหตุผลที่รูปแบบการใช้บัญชีแชร์เป็นวิธีที่ใช้งานได้เสมอในทุกกรณี

ตั้งค่าไคลเอนต์ เพราะนี่คือจุดที่การทำ self-hosted calendar มักจะล้มเหลว

iPhone และ iPad เปิด Settings จากนั้นไปที่ Calendar (ใน iOS เวอร์ชันใหม่จะอยู่ในหัวข้อ Apps) เลือก Calendar Accounts, Add Account, Other และ Add CalDAV Account ในช่อง Server ให้ใส่ cal.example.com ตามด้วยชื่อผู้ใช้และรหัสผ่าน ส่วน Description เป็นเพียงป้ายกำกับเท่านั้น หากระบบไม่ยอมบันทึก ให้เปิดบัญชีนั้นขึ้นมาอีกครั้ง มุมมองขั้นสูง (advanced view) จะแสดงตัวเลือก Use SSL, พอร์ต และ URL บัญชีแบบเต็ม การวาง URL โดยตรงจะช่วยข้ามขั้นตอนการค้นหาอัตโนมัติไปได้เลย

Android ระบบไม่มีไคลเอนต์ CalDAV ในตัว ให้ติดตั้ง DAVx⁵ จาก F-Droid หรือ Google Play จากนั้นเพิ่มบัญชีโดยใช้ URL หลักคือ https://cal.example.com/ พร้อมชื่อผู้ใช้ของคุณ แล้วเลือกปฏิทินที่ต้องการ DAVx⁵ จะเขียนข้อมูลลงในตัวจัดการปฏิทินของ Android ทำให้กิจกรรมต่างๆ ปรากฏในแอปปฏิทินที่คุณใช้งานอยู่แล้ว

Thunderbird เลือก New Calendar, On the Network จากนั้นใส่ชื่อผู้ใช้และตำแหน่ง https://cal.example.com/ ระบบจะแสดงรายการที่พบและถามว่าต้องการเพิ่มปฏิทินใดบ้าง

macOS ไปที่ System Settings, Internet Accounts, Add Other Account, CalDAV ตั้งค่า Account Type เป็น Manual จากนั้นใส่ชื่อผู้ใช้ รหัสผ่าน และที่อยู่เซิร์ฟเวอร์ตามที่กำหนด

CalDAV เป็นโปรโตคอลแบบ polling ในข้อกำหนดไม่มีระบบ push ดังนั้นกิจกรรมที่คุณเพิ่มบนแล็ปท็อปจะไปปรากฏบนโทรศัพท์ในการซิงค์รอบถัดไป ไม่ใช่ในวินาทีนั้นทันที ให้ตั้งค่าช่วงเวลาการซิงค์ในแต่ละไคลเอนต์ตามที่คุณยอมรับได้ และควรจำไว้ว่าการตั้งค่าช่วงเวลาที่สั้นเกินไปบนโทรศัพท์จะทำให้เปลืองแบตเตอรี่

สำรองข้อมูลที่จัดเก็บซึ่งเป็นเพียงไฟล์ธรรมดา

ภายใต้ Radicale ปฏิทินของคุณคือไดเรกทอรีของไฟล์ .ics โดยหนึ่งไฟล์ต่อหนึ่งเหตุการณ์ พร้อมด้วยไฟล์คุณสมบัติขนาดเล็กต่อหนึ่งคอลเลกชัน เครื่องมือใดก็ตามที่คัดลอกไดเรกทอรีสามารถใช้สำรองข้อมูลได้ และคุณสามารถเปิดไฟล์สำรองด้วย less เพื่อตรวจสอบว่ามีข้อมูลเหตุการณ์อยู่จริง นี่เป็นข้อได้เปรียบที่แท้จริงเมื่อเทียบกับการดัมพ์ฐานข้อมูลที่คุณไม่สามารถอ่านได้โดยตรง

sudo systemctl stop radicale
sudo tar czf /root/radicale-$(date +%F).tar.gz -C /var/lib/radicale collections
sudo systemctl start radicale

ให้หยุดการทำงานของเซอร์วิสในช่วงเวลาไม่กี่วินาทีที่ใช้ในการทำอาร์ไคฟ์ เพื่อป้องกันไม่ให้ไคลเอนต์เขียนข้อมูลค้างไว้ในขณะที่กำลังอ่านไฟล์ จากนั้นให้คัดลอกไฟล์อาร์ไคฟ์ออกจากเครื่อง เพราะการสำรองข้อมูลไว้บน VPS เครื่องเดียวกันจะไม่สามารถกู้คืนได้หากเกิดความล้มเหลวที่คุณกำลังเตรียมรับมือ การกู้คืนทำได้โดยย้อนขั้นตอนเดิม คือแตกไฟล์ sudo chown -R radicale:radicale /var/lib/radicale/collections แล้วเริ่มการทำงานของเซอร์วิส นอกจากนี้ ไคลเอนต์ทุกตัวยังเก็บสำเนาปฏิทินไว้ในเครื่อง ดังนั้นแล็ปท็อปที่ไม่ได้ซิงค์ข้อมูลตั้งแต่เกิดความล้มเหลวจึงถือเป็นสำเนาข้อมูลชุดที่สองของคุณ

เมื่อใดที่ Baikal หรือ Nextcloud เป็นตัวเลือกที่เหมาะสมกว่า

Baikal 0.12.1 ถูกปล่อยออกมาเมื่อวันที่ 5 สิงหาคม 2026 และต้องการ PHP 8.2 หรือใหม่กว่า ให้แตกไฟล์ไว้นอก web root และเปิดเผยเฉพาะไดเรกทอรี html เท่านั้น:

sudo apt install -y php-fpm php-sqlite3 php-xml php-mbstring php-curl unzip
cd /tmp
curl -LO https://github.com/sabre-io/Baikal/releases/download/0.12.1/baikal-0.12.1.zip
sudo unzip -q baikal-0.12.1.zip -d /srv
sudo chown -R www-data:www-data /srv/baikal/Specific /srv/baikal/config

ไดเรกทอรีทั้งสองนี้เป็นเพียงส่วนเดียวที่เว็บเซิร์ฟเวอร์ต้องเขียนข้อมูลลงไป ดังนั้นส่วนอื่นจึงไม่จำเป็นต้องมีสิทธิ์เขียน ภายใน nginx server block ของคุณ ส่วนที่เฉพาะเจาะจงสำหรับ Baikal มีดังนี้:

root /srv/baikal/html;
index index.php;

location ~ /(\.ht|Core|Specific|config) { deny all; }

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

location = /.well-known/caldav  { return 308 /dav.php; }
location = /.well-known/carddav { return 308 /dav.php; }

ให้ reload nginx แล้วเปิดเว็บไซต์ผ่านเบราว์เซอร์ ตัวช่วยติดตั้งจะสร้างบัญชีผู้ดูแลระบบและฐานข้อมูล SQLite ให้ การตั้งค่าฝั่งไคลเอนต์จะเหมือนกับของ Radicale โดยใช้ https://cal.example.com/ เป็นที่อยู่เซิร์ฟเวอร์ เนื่องจากกฎ well-known จะส่งการค้นหาไปยัง /dav.php โดยอัตโนมัติ

Nextcloud จะคุ้มค่ากับทรัพยากรที่ใช้ก็ต่อเมื่อคุณต้องการจัดการไฟล์และใช้งานแอปบนโทรศัพท์ด้วยบัญชีเดียวกันเท่านั้น โดยมี DAV root อยู่ที่ /remote.php/dav/ และใช้กฎการค้นหาเดียวกัน สำหรับบริการเหล่านี้ การรันในคอนเทนเนอร์จะช่วยแยกเวอร์ชันของ PHP ออกจากโฮสต์ของคุณได้: Docker Compose บน VPS ครอบคลุมถึงไฟล์ compose และการตั้งค่า reverse proxy ที่อยู่ด้านหน้า และ สิ่งที่ควรทำ self-hosting ในปี 2026 เป็นแหล่งข้อมูลที่เหมาะสมสำหรับการตัดสินใจว่าคุณต้องการก้าวไปไกลแค่ไหนในเส้นทางนี้

รูปแบบความล้มเหลวและข้อความที่คุณจะพบ

การซิงค์ทุกครั้งส่งค่า 401 กลับมา อาจเป็นไปได้ว่าไฟล์รหัสผ่านสูญเสียบัญชีผู้ใช้ไปให้กับ htpasswd -c อีกตัว หรือผู้ใช้ radicale ไม่สามารถอ่านไฟล์นั้นได้ ให้ตรวจสอบด้วย sudo -u radicale cat /etc/radicale/users หากพบข้อผิดพลาด permission denied นั่นคือสาเหตุ วิธีแก้ไขคือให้ใช้กลุ่ม radicale และตั้งค่า mode เป็น 640 นอกจากนี้ Radicale จะหน่วงเวลาหนึ่งวินาทีหลังจากการล็อกอินล้มเหลวแต่ละครั้งโดยค่าเริ่มต้น ดังนั้นไคลเอนต์ที่ใช้รหัสผ่านเก่าจะดูเหมือนทำงานช้าแทนที่จะถูกปฏิเสธการเชื่อมต่อ

nginx ตอบกลับ 405 เมื่อเรียกใช้ PROPFIND URL ดังกล่าวถูกให้บริการในฐานะไฟล์แบบ static ทำให้เมธอด WebDAV ไปไม่ถึง Radicale ให้ทดสอบ endpoint โดยตรงดังนี้:

curl -u you -X PROPFIND -H "Depth: 0" -i https://cal.example.com/you/

DAV collection ที่ทำงานปกติจะตอบกลับด้วย 207 Multi-Status หากเป็นค่าอื่นแสดงว่าคำขอถูกหยุดไว้ที่เว็บเซิร์ฟเวอร์

โทรศัพท์ไม่สามารถยืนยันบัญชีได้ แต่เบราว์เซอร์ใช้งานได้ปกติ มีสาเหตุหลักสองประการ ประการแรกคือการทำ well-known redirect หายไป ซึ่งทดสอบได้ด้วยคำสั่ง curl ด้านบน ประการที่สองคือ certificate chain ไม่สมบูรณ์ ซึ่งเบราว์เซอร์มักจะแก้ไขให้โดยอัตโนมัติด้วยการดึง intermediate certificate ที่ขาดหายไปมาให้ แต่ iOS ไม่ทำเช่นนั้น ให้ตรวจสอบจากเชลล์:

openssl s_client -connect cal.example.com:443 -servername cal.example.com </dev/null

ให้มองหา Verify return code: 0 (ok) หากตรวจสอบไม่ผ่าน แสดงว่าการตั้งค่า nginx ชี้ไปที่ cert.pem ในขณะที่ควรชี้ไปที่ fullchain.pem

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

ทุกอย่างหยุดทำงานหลังจากรีบูต บริการถูกเริ่มด้วยมือ sudo systemctl is-enabled radicale จะแสดงผลเป็น disabled และ sudo systemctl enable --now radicale จะแก้ไขปัญหานี้อย่างถาวร

FAQ

ฉันจำเป็นต้องใช้ TLS สำหรับเซิร์ฟเวอร์ CalDAV ที่โฮสต์เองจริงหรือไม่?

จำเป็นครับ CalDAV ใช้วิธีการยืนยันตัวตนแบบ HTTP Basic ซึ่งรหัสผ่านจะถูกส่งในรูปแบบ base64 ไปกับทุกคำขอ และ base64 นั้นสามารถถอดรหัสได้โดยง่าย นอกจากนี้ไคลเอนต์ยังบังคับใช้เรื่องนี้ด้วย เช่น macOS Calendar.app อาจปฏิเสธการส่งข้อมูลยืนยันตัวตนผ่าน HTTP ที่ไม่ปลอดภัยโดยไม่แจ้งเตือน และ iOS ก็มีพฤติกรรมเช่นเดียวกัน ส่งผลให้ดูเหมือนว่าบัญชีถูกบันทึกแล้วแต่ไม่เคยซิงค์ข้อมูลเลย sudo certbot --nginx -d cal.example.com คือหัวใจสำคัญของงานนี้

ทำไมโทรศัพท์ถึงเพิ่มบัญชีไม่ได้ในขณะที่ Thunderbird ใช้งานได้ปกติ?

Thunderbird จะใช้ URL เต็มที่คุณระบุ แต่โทรศัพท์จะมีช่องให้กรอกเซิร์ฟเวอร์เพียงช่องเดียว จึงต้องปฏิบัติตามมาตรฐานการค้นหาของ RFC 6764 โดยโทรศัพท์จะร้องขอ https://cal.example.com/.well-known/caldav และคาดหวังว่าจะได้รับการเปลี่ยนเส้นทาง (redirect) ไปยัง root ของ DAV หากไม่มีการเปลี่ยนเส้นทางดังกล่าว โทรศัพท์จะได้รับข้อผิดพลาด 404 และแจ้งว่าไม่สามารถยืนยันบัญชีได้ ให้เพิ่ม location = /.well-known/caldav { return 301 https://$host/; } ลงใน nginx จากนั้นตรวจสอบด้วย curl -sI https://cal.example.com/.well-known/caldav ว่าคุณได้รับสถานะ 301 และ header location หรือไม่

สองคนสามารถใช้ปฏิทินร่วมกันได้หรือไม่?

ได้ครับ วิธีที่น่าเชื่อถือที่สุดคือการใช้บัญชีล็อกอินร่วมกัน ให้สร้างบัญชีที่สามด้วย htpasswd นำปฏิทินที่ต้องการแชร์ไปไว้ภายใต้บัญชีนั้น แล้วเพิ่มบัญชีนี้เป็นบัญชี CalDAV ที่สองในอุปกรณ์ของแต่ละคน อีกทางเลือกหนึ่งคือการตั้งค่าไฟล์สิทธิ์ของ Radicale เพื่ออนุญาตให้ผู้ใช้ที่ระบุสามารถอ่านและเขียนในคอลเลกชันภายใต้ path ของผู้ใช้อื่นได้ แต่ไคลเอนต์ที่อ่านเฉพาะ calendar home set ของตนเองเท่านั้นจะไม่แสดงปฏิทินดังกล่าว ดังนั้นวิธีนี้จึงเหมาะกับ Thunderbird และ DAVx⁵ มากกว่า iOS

จะเกิดอะไรขึ้นกับกิจกรรมของฉันหาก VPS ล่ม?

สำหรับ Radicale ข้อมูลจะถูกจัดเก็บเป็นข้อความธรรมดา โดยเก็บไฟล์ .ics หนึ่งไฟล์ต่อหนึ่งกิจกรรมภายใต้ /var/lib/radicale/collections/collection-root/ ซึ่งคุณสามารถสำรองข้อมูลด้วย tar และอ่านข้อมูลได้ด้วย less การกู้คืนทำได้โดยการแตกไฟล์ chown -R radicale:radicale แล้วเริ่มการทำงานของ service ใหม่ นอกจากนี้ไคลเอนต์ที่ซิงค์ข้อมูลไว้ทุกเครื่องจะเก็บสำเนาในเครื่องด้วย ดังนั้นแล็ปท็อปที่ซิงค์ข้อมูลล่าสุดก่อนเกิดความล้มเหลวจะมีสำเนาของปฏิทินคุณเก็บไว้อีกชุดหนึ่งอย่างครบถ้วน

เซิร์ฟเวอร์ CalDAV ซิงค์รายชื่อผู้ติดต่อให้ฉันด้วยหรือไม่?

รายชื่อผู้ติดต่อจะใช้โปรโตคอล CardDAV ซึ่งเป็นโปรโตคอลในเครือที่กำหนดไว้ใน RFC 6352 โดยจะจัดเก็บไฟล์ vCard แทนที่จะเป็นกิจกรรม ทั้ง Radicale, Baikal และ Nextcloud ต่างให้บริการ CardDAV จากบัญชีเดียวกันและโฮสต์เนมเดียวกัน บน Android แอป DAVx⁵ จะซิงค์ทั้งปฏิทินและรายชื่อผู้ติดต่อจากบัญชีเดียว ส่วนบน iOS คุณต้องเพิ่มบัญชีที่สองประเภท CardDAV โดยใช้ข้อมูลยืนยันตัวตนชุดเดิม ซึ่งเป็นเหตุผลว่าทำไมการเปลี่ยนเส้นทาง /.well-known/carddav จึงจำเป็นต้องอยู่ใน config ของ nginx ควบคู่ไปกับส่วนของ CalDAV

#caldav#calendar#radicale#self-hosting#sync