วิธีติดตั้ง Nextcloud บน VPS ด้วย Docker
คู่มือติดตั้ง Nextcloud บน VPS โดยใช้ Docker Compose ร่วมกับ Postgres และ Redis พร้อมวิธีตั้งค่า TLS และระบบ Backup ข้อมูลให้ปลอดภัยและกู้คืนได้จริง
สิ่งที่คุณกำลังสร้าง
คู่มือนี้จะติดตั้ง Nextcloud บน VPS โดยใช้ Docker Compose พร้อมติดตั้ง Let's Encrypt TLS ไว้ด้านหน้า และตั้งค่าระบบสำรองข้อมูลที่สามารถกู้คืนได้จริง ระบบประกอบด้วย 4 container และ 1 proxy ได้แก่: image nextcloud อย่างเป็นทางการที่ทำงานบน loopback, Postgres สำหรับเก็บ metadata ของไฟล์ทั้งหมด, Redis สำหรับเก็บ file locks, Nextcloud image ตัวที่สองสำหรับรัน cron loop เท่านั้น และ nginx บน host เพื่อทำ TLS termination ไว้ด้านหน้าทั้งหมด การติดตั้งใช้เวลาประมาณ 20 นาที ซึ่งไม่ใช่ส่วนที่สำคัญที่สุด สิ่งสำคัญคือการตัดสินใจ 2 อย่างในช่วงชั่วโมงแรก เพื่อให้มั่นใจว่าไฟล์จะยังอยู่ครบในอีกหนึ่งปีข้างหน้า: การใช้ database จริงแทน SQLite และการสำรองข้อมูลที่รวมเอา data directory, database และ config.php ไว้เป็นชุดข้อมูลที่สอดคล้องกัน
การติดตั้งนี้ตั้งอยู่บนสมมติฐานว่าคุณใช้ Ubuntu 24.04 LTS หรือ Debian 13, ติดตั้ง Docker Engine พร้อม Compose v2 plugin จาก repository ของ Docker เอง และมี DNS A record (รวมถึง AAAA หากคุณใช้ IPv6) ที่ชี้ไปยัง cloud.example.com ของ VPS เรียบร้อยแล้ว คุณต้องมีเซิร์ฟเวอร์ที่คุณควบคุมเอง เนื่องจากไม่สามารถทำ TLS termination และการทำ database dump บนบริการ SaaS ของผู้อื่นได้
Sizing: ปัจจัยที่ใช้หน่วยความจำจริง
การใช้หน่วยความจำของ Nextcloud เกิดจาก 3 ปัจจัยหลัก ซึ่งไม่มีปัจจัยใดที่เป็นตัว "Nextcloud" โดยตรง
PHP workers. image -apache ประมวลผลแต่ละ request ที่เกิดขึ้นพร้อมกันผ่าน worker process ซึ่งมี PHP interpreter อยู่ภายใน แต่ละ worker อาจใช้หน่วยความจำเพิ่มขึ้นจนถึง PHP_MEMORY_LIMIT ก่อนที่ PHP จะสั่งหยุด request นั้น หน่วยความจำที่ถูกใช้งานจริงในกรณีที่แย่ที่สุดคือ จำนวน concurrent requests × memory limit โดยที่ desktop sync client จะเปิดการเชื่อมต่อแบบขนานหลายช่องทางต่อผู้ใช้หนึ่งราย ดังนั้น จำนวนการทำงานพร้อมกัน (Concurrency) คือตัวกำหนดขีดจำกัด ไม่ใช่จำนวนผู้ใช้งาน
The database. Postgres จะสร้าง backend แยกตามจำนวนการเชื่อมต่อ และเก็บ shared buffers ไว้ในหน่วยความจำ ปริมาณข้อมูลที่ใช้งาน (Working set) จะเพิ่มขึ้นตามจำนวน ไฟล์ ไม่ใช่จำนวนไบต์ โดยที่ oc_filecache จะเก็บข้อมูลหนึ่งแถวต่อหนึ่งไฟล์ต่อหนึ่งผู้ใช้ ดังนั้น ไฟล์ขนาดเล็กจำนวนหนึ่งแสนไฟล์ จะทำให้ database ทำงานหนักกว่าไฟล์ขนาดใหญ่จำนวนหนึ่งร้อยไฟล์
Preview generation. การสร้างรูปภาพขนาดเล็ก (Thumbnail) ต้องทำการถอดรหัสรูปภาพต้นฉบับลงในหน่วยความจำด้วยความละเอียดเต็ม ส่วนการสร้าง preview ของวิดีโอจะเรียกใช้งาน ffmpeg การรัน occ preview:generate-all จะทำให้เกิดการใช้งานหน่วยความจำพุ่งสูงขึ้นอย่างต่อเนื่อง ซึ่งเป็นสาเหตุหลักที่ทำให้ VPS ขนาดเล็กถูกระบบ OOM killer สั่งปิดการทำงาน
Redis ใช้ทรัพยากรน้อยเมื่อเทียบกับส่วนอื่น ส่วนประกอบอื่นๆ ที่อาจติดตั้งเพิ่มภายหลัง เช่น Collabora, full-text search หรือ antivirus scanner จะเป็น service แยกต่างหากที่มีการใช้หน่วยความจำเฉพาะตัว ดังนั้นควรนำมาคำนวณในแผนการจัดสรรทรัพยากรก่อนเริ่มใช้งาน
หากคุณมี RAM จำกัด ให้ดำเนินการดังนี้: ลดค่า PHP_MEMORY_LIMIT, จำกัดค่า preview_max_x / preview_max_y / preview_max_filesize_image, ลดจำนวนรูปแบบไฟล์ใน enabledPreviewProviders ให้เหลือเฉพาะรูปแบบที่ใช้งานจริง และตั้งค่า trashbin_retention_obligation กับ versions_retention_obligation เพื่อไม่ให้ directory เก็บข้อมูลขยายขนาดขึ้นหลายเท่าของขนาดไฟล์จริง นอกจากนี้ควรเพิ่ม swap file เนื่องจาก swap ทำงานช้า แต่การถูก OOM kill ระหว่างการอัปเกรดระบบนั้นเป็นปัญหาที่รุนแรงกว่า
ทำไม SQLite ถึงมีปัญหา
Nextcloud รองรับการใช้งาน SQLite และ official image จะใช้งานโดยอัตโนมัติ แต่ไม่ควรใช้งานด้วยวิธีนี้ เนื่องจาก SQLite จะทำการ serialize การเขียนข้อมูลด้วยการ lock ทั้งฐานข้อมูล ทำให้เขียนข้อมูลได้เพียงครั้งละหนึ่งรายการต่อหนึ่งไฟล์เท่านั้น Nextcloud มีการเขียนข้อมูลอยู่ตลอดเวลา เช่น file locks, activity rows, cache entries และ job state ในขณะที่ desktop client เพียงตัวเดียวที่กำลัง sync directory tree ก็มีการส่งคำสั่ง parallel requests จำนวนมาก เมื่อเกิดรูปแบบการทำงานเช่นนี้ จะทำให้เกิดข้อผิดพลาด SQLSTATE[HY000]: General error: 5 database is locked และ HTTP 500s ซึ่งปัญหาจะเริ่มปรากฏขึ้นเมื่อระบบเริ่มมีการใช้งานจริง
คุณสามารถเปลี่ยนไปใช้ฐานข้อมูลอื่นในภายหลังได้ด้วย occ db:convert-type แต่เป็นการ migration ข้อมูลที่มีอยู่จริงที่ใช้เวลานานและต้องทำให้สำเร็จทั้งหมดในคราวเดียว ควรเริ่มต้นใช้งานด้วย Postgres หรือ MariaDB
The Compose file
วางไฟล์นี้ไว้ใน /srv/nextcloud/compose.yaml โดยมีไฟล์ secrets เป็น .env ที่อยู่ในระดับเดียวกันในโหมด 600
services:
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- db:/var/lib/postgresql/data
environment:
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD}
app:
image: nextcloud:31-apache
restart: unless-stopped
depends_on: [db, redis]
ports:
- "127.0.0.1:8080:80"
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
environment:
POSTGRES_HOST: db
POSTGRES_DB: nextcloud
POSTGRES_USER: nextcloud
POSTGRES_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
REDIS_HOST_PASSWORD: ${REDIS_PASSWORD}
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.example.com
TRUSTED_PROXIES: 172.16.0.0/12
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.example.com
APACHE_DISABLE_REWRITE_IP: "1"
PHP_MEMORY_LIMIT: 512M
PHP_UPLOAD_LIMIT: 10G
cron:
image: nextcloud:31-apache
restart: unless-stopped
entrypoint: /cron.sh
depends_on: [db, redis]
volumes:
- html:/var/www/html
- /srv/nextcloud/data:/var/www/html/data
volumes:
db:
html:ควรระบุ major tag และตรวจสอบ tag ปัจจุบันบน Docker Hub ก่อนคัดลอก 31 ไปใช้งานโดยตรง หากใช้ latest อาจทำให้เกิดการอัปเกรดข้าม major version ใน docker compose pull รุ่นถัดไป ซึ่ง Nextcloud ไม่รองรับการทำงานดังกล่าว
สาเหตุที่กำหนดให้ directory ของข้อมูลเป็น bind mount แทนที่จะเป็น named volume เนื่องจากความสะดวกในการใช้เครื่องมือ backup เข้าถึง path ได้โดยตรง โปรดสร้าง directory ด้วย UID ตาม www-data ของ image และกำหนด permission ตามที่ Nextcloud ต้องการ:
sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/dataโปรดสังเกตการ publish port: 127.0.0.1:8080:80 เนื่องจาก Docker จะ publish port โดยการเขียน DNAT rules ซึ่งจะถูกประมวลผลก่อนที่ packet จะผ่าน chain INPUT ของ ufw ดังนั้นการใช้ 8080:80 แบบปกติจะทำให้ Nextcloud ที่ไม่ได้เข้ารหัสถูกเปิดสู่สาธารณะโดยไม่คำนึงถึงการตั้งค่า ufw การ bind ไปที่ loopback จะช่วยป้องกันไม่ให้เข้าถึงผ่าน public interface ได้ จากนั้น firewall จะต้องอนุญาตเฉพาะ proxy เท่านั้น และหากคุณไม่ต้องการเปิด SSH ไว้บนอินเทอร์เน็ตสาธารณะ การ เชื่อมต่อ VPS ผ่าน self-hosted WireGuard VPN จะช่วยให้คุณสามารถลบ port 22 ออกจาก public rules ได้ทั้งหมด
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableเริ่มการทำงานด้วย docker compose up -d แล้วตรวจสอบผ่าน docker compose logs -f app ในการเริ่มระบบครั้งแรก ระบบจะคัดลอก application tree ทั้งหมดลงใน volume และเริ่มรัน installer โดย container จะไม่ตอบสนองใดๆ จนกว่ากระบวนการนี้จะเสร็จสิ้น
TLS และ reverse proxy
ติดตั้ง nginx และ certbot จาก distro จากนั้นสร้าง server block สำหรับ port-80 แบบปกติพร้อมค่า server_name ที่ถูกต้อง แล้วให้ certbot ทำการ rewrite ไฟล์ดังกล่าว กลไกของ HTTP-01 challenge, ตัวตั้งเวลาการต่ออายุ (renewal timer) และรูปแบบความผิดพลาด (failure modes) มีรายละเอียดทั้งหมดใน issuing Let's Encrypt certificates with certbot and nginx on Ubuntu 24.04:
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.comCertbot จะเพิ่มบรรทัด ssl_certificate และการ redirect จาก :80 → :443 พร้อมทั้งติดตั้ง systemd timer เพื่อต่ออายุใบรับรองที่มีอายุ 90 วัน ตรวจสอบการมีอยู่ของไฟล์ด้วย systemctl list-timers | grep certbot — หาก renewal timer ไม่ถูกเปิดใช้งาน จะส่งผลให้ใบรับรองหมดอายุภายใน 90 วัน
ตัว proxy block:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name cloud.example.com;
# certbot manages ssl_certificate / ssl_certificate_key here
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
client_max_body_size 10G;
client_body_timeout 300s;
location = /.well-known/carddav { return 301 /remote.php/dav; }
location = /.well-known/caldav { return 301 /remote.php/dav; }
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_request_buffering off;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}สำหรับ nginx 1.25 และเวอร์ชันที่ใหม่กว่า ให้เพิ่ม http2 on; ส่วน Ubuntu 24.04 ใช้เวอร์ชันเก่าซึ่งต้องใช้ listen 443 ssl http2; แทน สามารถใช้ nginx -t เพื่อตรวจสอบว่าเวอร์ชันที่ติดตั้งรองรับคำสั่งใด
client_max_body_size และการตั้งค่า long read timeouts คือสิ่งที่ช่วยป้องกันไม่ให้การอัปโหลดไฟล์ขนาดใหญ่หยุดชะงักกลางคัน ส่วน proxy_request_buffering off จะทำการ stream การอัปโหลดแทนการเขียนไฟล์ทั้งหมดลงใน disk ของ proxy ก่อน
การใช้ nginx บน host เป็นวิธีที่ง่ายที่สุดสำหรับการใช้งานเพียงแอปพลิเคชันเดียว หาก Nextcloud ต้องใช้งาน VPS ร่วมกับ container อื่นๆ running Traefik as a Docker Compose reverse proxy for multiple apps จะย้ายการจัดการ routing และการออกใบรับรองไปไว้ใน container labels โดยต้องคำนึงถึง client_max_body_size และการตั้งค่า timeout ในรูปแบบของ middleware และ transport settings แทน
trusted_proxies และ overwriteprotocol
นี่คือจุดที่ทำให้การติดตั้ง Nextcloud แบบ self-hosted ส่วนใหญ่เกิดปัญหา โดยอาการที่ปรากฏมักดูเหมือนไม่เกี่ยวข้องกับสาเหตุที่แท้จริง
X-Forwarded-Proto: https จะถูกใช้งานก็ต่อเมื่อ request ส่งมาจาก address ที่ระบุไว้ใน trusted_proxies เท่านั้น หากไม่ถูกใช้งาน Nextcloud จะเข้าใจว่า request เป็นแบบ HTTP ธรรมดา และจะสร้าง URL เป็น http:// จากนั้น proxy จะทำการ redirect ไปยัง HTTPS และ browser จะดำเนินการตามนั้น ส่งผลให้ Nextcloud สร้าง http:// ขึ้นมาอีกครั้ง จนเกิดเป็น redirect loop ทั้งนี้ OVERWRITEPROTOCOL: https จะช่วยกำหนด scheme ให้คงที่เสมอ
ข้อผิดพลาดใน TRUSTED_PROXIES คือ address ที่ Nextcloud มองเห็นนั้น ไม่ใช่ 127.0.0.1 เนื่องจาก nginx ทำงานบน host และเชื่อมต่อผ่าน port ที่เปิดไว้ ทำให้ container มองเห็นเป็น Docker bridge gateway ซึ่งอยู่ในช่วง 172.x วิธีการหา subnet ที่แท้จริงคือ:
docker network inspect nextcloud_default \
-f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'ให้นำ CIDR นั้น (หรือ 172.16.0.0/12 ที่ครอบคลุมช่วงดังกล่าว) ไปใส่ใน TRUSTED_PROXIES หากตั้งค่ากว้างเกินไป client ใดๆ อาจปลอมแปลง X-Forwarded-For ได้ แต่หากตั้งค่าผิด ทุกการ login จะปรากฏว่ามาจาก address ของ gateway ซึ่งจะทำให้ระบบป้องกัน brute-force บล็อกการเข้าถึง instance ทั้งหมด และในหน้า admin overview จะแสดงข้อความ "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy."
OVERWRITECLIURL มีความสำคัญต่อ cron container เนื่องจากไม่มี request ขาเข้าเพื่อใช้ระบุ hostname หากไม่มีการตั้งค่านี้ งาน background จะสร้าง link เป็น localhost และการแจ้งเตือนผ่าน email จะส่ง URL ที่ใช้งานไม่ได้มาให้
Background jobs: cron, not AJAX
ระบบรันงานเริ่มต้นของ Nextcloud คือ AJAX โดยงานจะทำงานเมื่อมีการโหลดหน้าเว็บ หากไม่มีผู้ใช้งานในช่วงเวลา 04:00 การลบไฟล์ในถังขยะ การล้างข้อมูลเวอร์ชัน การสร้างรูปตัวอย่าง และการลองใหม่ของระบบ federated จะหยุดชะงัก ส่งผลให้ขนาดของ data directory เพิ่มขึ้นอย่างต่อเนื่อง อาการเบื้องต้นที่พบคือพื้นที่จัดเก็บข้อมูลไม่ลดลงเลย บริการ cron ที่กล่าวถึงข้างต้นจะรัน loop ของ /cron.sh กับ volume ชุดเดียวกัน เพื่อให้ Nextcloud รอรับการทำงานจากระบบนี้ ให้ใช้คำสั่ง:
docker compose exec -u www-data app php occ background:cronคำสั่ง occ ทุกคำสั่งจะมีรูปแบบดังนี้: docker compose exec -u www-data app php occ <command> แนะนำให้ตั้งค่า alias ไว้เพื่อความสะดวกในการใช้งาน
Backups: three things, or none
การสำรองข้อมูลเฉพาะ filesystem จะไม่สามารถกู้คืนระบบที่เสียหายได้สำเร็จ เนื่องจาก data directory เก็บข้อมูลดิบ แต่ Postgres เก็บ file cache, shares, users และ app state ส่วน config.php เก็บ database credentials, instance ID และ password salt หากกู้คืนเฉพาะไฟล์โดยไม่มี database ตัว Nextcloud จะไม่สามารถมองเห็นไฟล์เหล่านั้นได้ หากกู้คืน database โดยไม่มี config.php จะไม่สามารถเปิด database ได้ และหากกู้คืน database รุ่นเก่าลงใน data directory รุ่นใหม่ จะทำให้ share ชี้ไปยังไฟล์ที่เปลี่ยนตำแหน่งไปแล้ว
ต้องสำรองข้อมูลทั้งสามส่วนจาก instance ที่อยู่ในสถานะ quiesced:
#!/usr/bin/env bash
set -euo pipefail
cd /srv/nextcloud
DEST="/var/backups/nextcloud/$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$DEST"
occ() { docker compose exec -T -u www-data app php occ "$@"; }
occ maintenance:mode --on
trap 'occ maintenance:mode --off' EXIT
docker compose exec -T db \
pg_dump -U nextcloud --clean --if-exists nextcloud | gzip > "$DEST/db.sql.gz"
docker compose exec -T app \
tar -C /var/www/html -cf - config custom_apps themes > "$DEST/app.tar"
rsync -a --delete /srv/nextcloud/data/ /var/backups/nextcloud/data/Maintenance mode ช่วยให้ข้อมูลใน dump และไฟล์ที่ copy ตรงกัน หากข้ามขั้นตอนนี้ ข้อมูลใน database อาจอ้างอิงถึงไฟล์ที่ rsync ยังคัดลอกไปไม่ถึง โปรดทราบว่า script จะเก็บ database dumps แบบระบุ timestamp ไว้ แต่จะเก็บ data directory ไว้เพียงชุดเดียวแบบ rolling mirror โดย rsync --delete จะเขียนทับทุกครั้งที่รัน ดังนั้นจะมีเพียง dump ล่าสุดเท่านั้นที่ใช้งานร่วมกับไฟล์ที่คัดลอกมาได้
จากนั้นต้องนำข้อมูลออกไปเก็บนอกเครื่อง การสำรองข้อมูลที่เก็บไว้ใน VPS เครื่องเดียวกับระบบที่สำรองข้อมูลถือเป็นเพียงการ copy ไม่ใช่การ backup วิธีแก้ปัญหาทั่วไปคือการใช้ restic กับ object storage หรือ host เครื่องที่สอง ซึ่งระบบ deduplication จะจัดการ data directory ได้ดีกว่าการทำ tarball รายวัน รายละเอียดการติดตั้งทั้งหมดตั้งแต่การ init repository ไปจนถึงการตั้งค่า nightly timer และการซ้อมกู้คืน ดูได้ที่ off-box VPS backups with restic
การกู้คืนไม่ใช่เพียงแค่การทำกระบวนการย้อนกลับ เนื่องจาก stack ที่เพิ่งเริ่มทำงานใหม่จะรัน installer และสร้าง config.php ชุดใหม่ ซึ่งประกอบด้วย instance ID และ password salt ชุดใหม่ การนำ dump มา import ลงใน identity ใหม่นี้จะทำให้ session และ share tokens เสียหาย ต้องนำ identity เดิมกลับมาคืนก่อนตามลำดับนี้:
docker compose up -d && docker compose stop app cron # create the volumes, then halt the app
sudo rsync -a --delete /var/backups/nextcloud/data/ /srv/nextcloud/data/
docker compose run --rm -T --entrypoint "" app \
tar -C /var/www/html -xf - < app.tar # the original config.php returns
gunzip -c db.sql.gz | docker compose exec -T db psql -U nextcloud -d nextcloud
docker compose start app cron
docker compose exec -T -u www-data app php occ maintenance:mode --off
docker compose exec -T -u www-data app php occ files:scan --allfiles:scan จะทำการปรับปรุง file cache ให้ตรงกับข้อมูลที่มีอยู่จริงบน disk ควรทดลองฝึกซ้อมขั้นตอนเหล่านี้บน VPS สำรองหนึ่งเครื่องก่อนใช้งานจริง
การอัปเกรด: ทำทีละเวอร์ชันหลักเท่านั้น
Nextcloud รองรับการอัปเกรดเพียงหนึ่งเวอร์ชันหลักในแต่ละครั้ง การข้ามจากเวอร์ชัน 29 ไปยัง 31 จะทำให้เกิดข้อผิดพลาดโดยแสดงรหัส Exception: Updates between multiple major versions and downgrades are unsupported. และทำให้ระบบค้างอยู่ใน maintenance mode
ขั้นตอนการอัปเกรดผ่าน Docker คือ: ทำการสำรองข้อมูล (backup), แก้ไข tag จาก 31 เป็น 32 ในทั้ง service app และ cron, จากนั้นรัน docker compose pull && docker compose up -d และตามด้วย docker compose logs -f app ตัว image entrypoint จะตรวจสอบความเข้ากันได้ของโค้ดเวอร์ชันใหม่กับข้อมูลเดิม และจะรัน occ upgrade โดยอัตโนมัติ ห้ามขัดจังหวะการทำงานนี้ เมื่อ log หยุดทำงานแล้ว ให้รัน docker compose exec -u www-data app php occ status เพื่อตรวจสอบ versionstring และตรวจสอบว่าแอปพลิเคชันต่างๆ กลับมาเปิดใช้งาน (enabled) ตามปกติ
กฎสองข้อเพื่อความปลอดภัย: อัปเกรดหนึ่งเวอร์ชันหลัก ตรวจสอบความเรียบร้อย แล้วจึงอัปเกรดเวอร์ชันถัดไป และห้ามแก้ไข tag ใน service app โดยไม่แก้ไข cron ให้ตรงกัน เพราะการใช้ Nextcloud สองเวอร์ชันที่ต่างกันกับฐานข้อมูลเดียวจะทำให้ข้อมูลเสียหาย (corruption)
ข้อผิดพลาดที่คุณจะพบเจอจริง
"Your data directory is readable by other users. Please change the permissions to 0770." Directory ที่ทำ bind-mount มีสิทธิ์การอ่าน (read bits) สำหรับ group หรือ world ตั้งไว้ sudo chmod 0770 /srv/nextcloud/data และ sudo chown -R 33:33 /srv/nextcloud/data
"Your data directory is invalid. Ensure there is a file called .ocdata in the root." bind mount ชี้ไปยังตำแหน่งที่ Nextcloud ยังไม่ได้ทำ initialization เช่น พิมพ์ path ผิด หรือมีการเปลี่ยน directory ว่างเปล่ามาแทนที่ directory เดิมที่ใช้งานอยู่ ให้ตรวจสอบว่า host path ตรงกับบรรทัด volume หรือไม่
"Access through untrusted domain." hostname ใน request ไม่ได้ระบุไว้ใน trusted_domains โดย NEXTCLOUD_TRUSTED_DOMAINS จะใช้เฉพาะตอนติดตั้งครั้งแรกเท่านั้น หากต้องการตั้งค่าภายหลังให้ใช้ occ config:system:set trusted_domains 1 --value=cloud.example.com
502 Bad Gateway พร้อมกับ connect() failed (111: Connection refused) while connecting to upstream ใน /var/log/nginx/error.log เกิดจาก nginx ไม่สามารถเชื่อมต่อกับบริการใดๆ บน 127.0.0.1:8080 ได้ สาเหตุอาจเกิดจาก container กำลังอยู่ในขั้นตอน initialising (ตรวจสอบ docker compose logs app), container หยุดทำงาน (docker compose ps), หรือบรรทัดการ publish port ไม่ตรงกับ port proxy_pass ให้ตรวจสอบด้วย ss -ltnp | grep 8080
เกิด redirect loop หรือคำเตือน "insecure" ใน admin overview เกิดจากขาด OVERWRITEPROTOCOL: https หรือ TRUSTED_PROXIES ไม่ได้ระบุ Docker gateway subnet ไว้ ให้ดูรายละเอียดในส่วนของ proxy ด้านบน
LockedException: "files/..." is locked หากตั้งค่า REDIS_HOST ไว้ image จะกำหนดให้ Redis เป็น locking backend ซึ่งจะช่วยลดปัญหา stale locks ได้ หากไม่ได้ตั้งค่าไว้ lock จะถูกเก็บไว้ใน database table oc_file_locks ซึ่งหาก request ถูกขัดจังหวะขณะเขียนข้อมูล จะทำให้มีแถวข้อมูลค้างอยู่ ให้ตรวจสอบว่ามีการใช้งาน Redis จริงหรือไม่ โดย occ config:system:get memcache.locking ควรคืนค่าเป็น Redis class ก่อนที่จะทำการลบแถวข้อมูล lock ด้วยตนเอง
"The PHP memory limit is below the recommended value of 512MB." ให้เพิ่มค่า PHP_MEMORY_LIMIT และสร้าง container ใหม่ โปรดระวังผลกระทบต่อเพดานการใช้งานสูงสุด (worst-case ceiling) ของคุณ
ปัญหาเมื่อระบบมีขนาดใหญ่ขึ้น
อุปสรรคแรกคือ directory ของข้อมูลมีขนาดใหญ่เกินกว่า volume ที่กำหนดไว้ การขยายขนาด volume บน VPS ต้องทำทั้งการ resize และการขยาย filesystem ซึ่งการจัดการภายหลังเมื่อพื้นที่เต็ม 100% นั้นทำได้ยากกว่ามาก ควรตั้งค่าการแจ้งเตือนการใช้งาน disk ไว้ตั้งแต่เนิ่นๆ
อุปสรรคที่สองคือ oc_filecache การแสดงรายการไฟล์และการสแกนเพื่อ sync จะช้าลงตามจำนวนแถวของข้อมูล วิธีแก้ไขคือการจัดการด้าน database: ควรเก็บ Postgres ไว้บน storage ที่มีความเร็วสูง จัดสรร shared memory ให้เพียงพอ และใช้การตั้งค่า retention เพื่อลบข้อมูลขยะและเวอร์ชันเก่า แทนการปล่อยให้ข้อมูลสะสมไปเรื่อยๆ
อุปสรรคที่สามคือการสร้าง preview ที่แย่งทรัพยากรจากส่วนอื่น สำหรับเครื่องที่มีสเปกต่ำ ควรจำกัดจำนวน preview providers และไม่ควรรัน occ preview:generate-all ในช่วงเวลาทำงาน
นอกเหนือจากนั้น ข้อเท็จจริงคือฟีเจอร์เสริมต่างๆ ต้องการเครื่องแยกต่างหาก Collabora และ full-text search เป็นบริการที่ต้องรันค้างไว้และมีรูปแบบการใช้ memory เฉพาะตัว การติดตั้งบริการเหล่านี้บนเครื่องเดียวกับที่เก็บไฟล์ข้อมูลเพียงชุดเดียวจะทำให้ขอบเขตความเสียหาย (failure domain) กว้างขึ้นโดยไม่มีประโยชน์ หาก volume เริ่มมีขนาดไม่เหมาะสม ให้ย้ายการเก็บไฟล์ไปยัง primary storage ที่รองรับ S3 — และโปรดทราบว่าวิธีนี้จะทำให้การ backup ทำได้ยากขึ้น ไม่ใช่สะดวกขึ้น เนื่องจาก database ยังคงเก็บ metadata ไว้ และต้องทำการ dump ข้อมูลให้สอดคล้องกับข้อมูลใน bucket
เมื่อ instance เริ่มมีผู้ใช้งานจริง ให้ติดตั้ง Uptime Kuma ไว้ด้านหน้า เพื่อให้คุณทราบถึงปัญหา downtime ก่อนที่ sync clients จะพบปัญหา สำหรับ private cloud การใช้งานร่วมกับ mail server ของคุณเอง จะทำงานได้ดี และหากคุณไม่ต้องการเชื่อมต่อบริการต่างๆ ด้วยตนเอง Cloudron, CasaOS and Coolify เป็นแพลตฟอร์มที่ช่วยจัดการเรื่องนี้ให้คุณได้
FAQ
สามารถรัน Nextcloud ด้วย SQLite แทน Postgres ได้หรือไม่?
สามารถทำได้ และ official image รองรับการใช้งานนี้ แต่หาก desktop sync client ส่งคำขอแบบ parallel จะทำให้เกิดข้อผิดพลาด SQLSTATE[HY000]: General error: 5 database is locked และ HTTP 500 เนื่องจาก SQLite จะทำการ lock การเขียนข้อมูลทั้งฐานข้อมูล ในขณะที่ Nextcloud มีการเขียนข้อมูลตลอดเวลา เช่น file locks, activity rows และ job state แนะนำให้เริ่มต้นด้วย Postgres หรือ MariaDB แม้จะมี occ db:convert-type แต่การย้ายข้อมูลจาก SQLite ไปยังฐานข้อมูลอื่นนั้นทำได้ยากและต้องทำทั้งหมดในครั้งเดียว
Nextcloud VPS จำเป็นต้องใช้ RAM เท่าไหร่?
ควรพิจารณาจากจำนวนการเชื่อมต่อพร้อมกัน (concurrency) ไม่ใช่จำนวนผู้ใช้ ปริมาณ memory ที่ใช้จริงในกรณีที่แย่ที่สุดคือจำนวนคำขอที่ส่งมาพร้อมกันคูณด้วย PHP_MEMORY_LIMIT บวกกับ postgres shared buffers และ backend หนึ่งตัวต่อหนึ่งการเชื่อมต่อ รวมถึงปริมาณการใช้งานที่เพิ่มขึ้นจากการสร้าง preview หากมี RAM 2 GB จะสามารถรัน instance ขนาดเล็กสำหรับใช้งานในครัวเรือนได้หากจำกัดการสร้าง preview และเพิ่ม swap แต่หากติดตั้ง Collabora หรือ full-text search จะต้องคำนวณทรัพยากรสำหรับบริการเหล่านี้แยกต่างหาก
ทำไมการอัปโหลดไฟล์ขนาดใหญ่ถึงล้มเหลวเมื่อใช้งานผ่าน nginx reverse proxy?
สาเหตุส่วนใหญ่เกิดจากการตั้งค่าสองส่วนบน proxy ได้แก่ client_max_body_size ที่ตั้งค่าเริ่มต้นไว้ที่ 1 MB ซึ่งทำให้คำขอถูกตัด และค่า proxy_read_timeout / proxy_send_timeout ที่สั้นเกินไปจนทำให้การส่งข้อมูลระยะยาวหยุดชะงักกลางคัน ควรตั้งค่าทั้งสองส่วนให้มีขนาดเพียงพอ ตั้งค่า proxy_request_buffering off เป็น stream แทนการ spool และเพิ่มค่า PHP_UPLOAD_LIMIT ใน app container ให้สอดคล้องกัน
ทำไม Nextcloud ถึงเกิดการ redirect วนลูป หรือแจ้งเตือนเกี่ยวกับ reverse proxy?
เนื่องจาก container ไม่เห็น nginx ที่ 127.0.0.1 แต่จะเห็นเป็น Docker bridge gateway ในช่วง 172.x เมื่อที่อยู่ดังกล่าวไม่อยู่ใน TRUSTED_PROXIES จะทำให้ header X-Forwarded-Proto: https ถูกละเลย ส่งผลให้ Nextcloud สร้าง URL แบบ http:// และ proxy จะส่งคำขอนั้นวนกลับมา ให้ตั้งค่า TRUSTED_PROXIES เป็น subnet ของ bridge ที่ถูกต้องและกำหนดค่า OVERWRITEPROTOCOL: https
สามารถอัปเกรด Nextcloud จากเวอร์ชัน 29 ไปเป็น 31 โดยตรงได้หรือไม่?
ไม่ได้ Nextcloud รองรับการอัปเกรดเพียงครั้งละหนึ่ง major version เท่านั้น การข้ามเวอร์ชันจะทำให้เกิดข้อผิดพลาดที่ Updates between multiple major versions and downgrades are unsupported. และทำให้ instance เข้าสู่ maintenance mode วิธีที่ถูกต้องคือทำการ backup, อัปเดต tag ขึ้นทีละหนึ่ง major version ทั้งในบริการ app และ cron, ทำการ docker compose pull && docker compose up -d, ตรวจสอบด้วย occ status แล้วจึงทำซ้ำขั้นตอนเดิมในเวอร์ชันถัดไป