SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

วิธีติดตั้ง Nextcloud บน VPS ด้วย Docker Compose และ TLS

เรียนรู้วิธีติดตั้ง Nextcloud บน VPS ด้วย Docker Compose พร้อม Postgres, Redis และ TLS ผ่าน Nginx รวมถึงขั้นตอนการสำรองข้อมูลและอัปเกรดที่ช่วยให้ข้อมูลของคุณปลอดภัยและกู้คืนได้จริง

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

คู่มือนี้จะแนะนำการรัน Nextcloud บน VPS ด้วย Docker Compose โดยวาง Let's Encrypt TLS ไว้ด้านหน้า และตั้งค่าการสำรองข้อมูลที่สามารถกู้คืนได้จริง ระบบประกอบด้วยคอนเทนเนอร์ 4 ตัวและพร็อกซีหนึ่งตัว: อิมเมจ nextcloud อย่างเป็นทางการที่ฟังบน loopback, Postgres ที่เก็บข้อมูล metadata ของไฟล์ทั้งหมด, Redis ที่ทำหน้าที่จัดการ file locks, อิมเมจ Nextcloud ตัวที่สองที่รันเฉพาะ cron loop และ nginx บนโฮสต์ที่ทำหน้าที่จัดการ TLS termination ให้กับทุกบริการ การติดตั้งใช้เวลาเพียง 20 นาที ซึ่งไม่ใช่ส่วนที่สำคัญที่สุด การตัดสินใจสองอย่างในชั่วโมงแรกจะเป็นตัวกำหนดว่าคุณจะยังคงมีไฟล์ของคุณอยู่หรือไม่ในอีกหนึ่งปีข้างหน้า นั่นคือการใช้ฐานข้อมูลจริงแทน SQLite และการสำรองข้อมูลที่เก็บทั้งไดเรกทอรีข้อมูล, ฐานข้อมูล และ 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 และ dump ฐานข้อมูลบนบริการ SaaS ของผู้อื่นได้

การจัดสรรทรัพยากร: สิ่งที่ใช้หน่วยความจำจริง

การใช้หน่วยความจำของ Nextcloud ขึ้นอยู่กับปัจจัยหลัก 3 ประการ ซึ่งไม่มีปัจจัยใดที่เป็นตัว "Nextcloud" โดยตรง

PHP workers: อิมเมจ -apache จะให้บริการคำขอที่เข้ามาพร้อมกันแต่ละรายการผ่านกระบวนการทำงาน (worker process) ที่รัน PHP interpreter อยู่ โดย worker แต่ละตัวอาจใช้หน่วยความจำเพิ่มขึ้นได้สูงสุดถึง PHP_MEMORY_LIMIT ก่อนที่ PHP จะยุติคำขอนั้น ดังนั้น หน่วยความจำที่ใช้จริงในกรณีที่เลวร้ายที่สุดจะอยู่ที่ประมาณ จำนวนคำขอที่เข้ามาพร้อมกัน × ขีดจำกัดหน่วยความจำ และไคลเอนต์สำหรับซิงค์บนเดสก์ท็อปจะเปิดการเชื่อมต่อแบบขนานหลายรายการต่อผู้ใช้หนึ่งคน ดังนั้นจำนวนการทำงานพร้อมกัน (concurrency) ไม่ใช่จำนวนผู้ใช้ คือสิ่งที่กำหนดเพดานการใช้งาน

ฐานข้อมูล: Postgres จะแยกกระบวนการทำงาน (fork) ออกมาหนึ่งรายการต่อการเชื่อมต่อและเก็บ shared buffers ไว้ในหน่วยความจำ ชุดข้อมูลที่ใช้งานจริงจะขยายตัวตามจำนวน ไฟล์ ไม่ใช่จำนวนไบต์ โดย oc_filecache จะเก็บข้อมูลหนึ่งแถวต่อหนึ่งไฟล์ต่อหนึ่งผู้ใช้ ดังนั้นไฟล์ขนาดเล็กจำนวนหนึ่งแสนไฟล์จึงใช้ทรัพยากรฐานข้อมูลมากกว่าไฟล์ขนาดใหญ่จำนวนหนึ่งร้อยไฟล์

การสร้างภาพตัวอย่าง (Preview generation): การสร้างภาพย่อ (thumbnail) จะถอดรหัสไฟล์ต้นฉบับลงในหน่วยความจำที่ความละเอียดเต็มขนาด ส่วนการสร้างภาพตัวอย่างวิดีโอจะเรียกใช้ ffmpeg การรัน occ preview:generate-all จะทำให้เกิดการใช้ทรัพยากรพุ่งสูงขึ้นซ้ำๆ อย่างต่อเนื่อง และเป็นสาเหตุที่พบบ่อยที่สุดที่ทำให้ VPS ขนาดเล็กถูก OOM killer สั่งยุติการทำงาน

Redis ใช้ทรัพยากรค่อนข้างน้อย ส่วนบริการอื่นๆ ที่คุณติดตั้งเพิ่มในภายหลัง เช่น Collabora, full-text search หรือโปรแกรมสแกนไวรัส จะเป็นบริการที่รันแยกต่างหากและใช้หน่วยความจำของตัวเอง ซึ่งคุณควรนำมาคำนวณในแผนการจัดสรรทรัพยากรก่อนที่จะเปิดใช้งาน

หากคุณมี RAM จำกัด สิ่งที่สามารถปรับแต่งได้คือ: ลดค่า PHP_MEMORY_LIMIT, จำกัดค่า preview_max_x / preview_max_y / preview_max_filesize_image, ปรับแต่ง enabledPreviewProviders ให้เหลือเฉพาะรูปแบบไฟล์ที่คุณเปิดดูจริง และตั้งค่า trashbin_retention_obligation กับ versions_retention_obligation เพื่อไม่ให้ไดเรกทอรีข้อมูลขยายตัวจนมีขนาดใหญ่กว่าไฟล์จริงของคุณหลายเท่าโดยไม่รู้ตัว นอกจากนี้ควรเพิ่ม swap file แม้ว่า swap จะทำงานช้า แต่การถูก OOM kill ในระหว่างการอัปเกรดนั้นเลวร้ายกว่ามาก

เหตุใด SQLite จึงมีปัญหา

Nextcloud มาพร้อมกับการรองรับ SQLite และอิมเมจอย่างเป็นทางการก็สามารถใช้งานได้ทันที แต่ไม่ควรทำเช่นนั้น SQLite จะทำการจัดลำดับการเขียนข้อมูลด้วยการล็อกฐานข้อมูลทั้งไฟล์ ซึ่งอนุญาตให้เขียนได้ทีละหนึ่งรายการเท่านั้นสำหรับทั้งไฟล์ Nextcloud มีการเขียนข้อมูลอยู่ตลอดเวลา ทั้งการล็อกไฟล์, แถวข้อมูลกิจกรรม, รายการแคช, สถานะงาน และไคลเอนต์เดสก์ท็อปเพียงเครื่องเดียวที่ซิงค์โครงสร้างไดเรกทอรีก็ส่งคำขอแบบขนานจำนวนมาก ภายใต้รูปแบบการทำงานดังกล่าว คุณจะพบกับ SQLSTATE[HY000]: General error: 5 database is locked และข้อผิดพลาด HTTP 500 โดยความล้มเหลวจะปรากฏขึ้นทันทีเมื่ออินสแตนซ์เริ่มถูกใช้งานจริง

การแปลงฐานข้อมูลในภายหลังสามารถทำได้ด้วย occ db:convert-type แต่เป็นกระบวนการย้ายข้อมูลที่ใช้เวลานานและต้องทำจนเสร็จสิ้นในคราวเดียวบนชุดข้อมูลที่กำลังใช้งานอยู่ ดังนั้นควรเริ่มต้นด้วย Postgres หรือ MariaDB ตั้งแต่แรก

ไฟล์ Compose

ให้วางเนื้อหานี้ไว้ใน /srv/nextcloud/compose.yaml โดยเก็บข้อมูลลับไว้ในไฟล์ .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) ให้ชัดเจนและตรวจสอบเวอร์ชันปัจจุบันบน Docker Hub ก่อนที่จะคัดลอก 31 ไปใช้งาน การใช้ latest อาจทำให้เกิดการอัปเกรดข้ามเวอร์ชันหลักในการสั่ง docker compose pull ครั้งถัดไป ซึ่ง Nextcloud ไม่รองรับการดำเนินการดังกล่าว

ไดเรกทอรีข้อมูลถูกตั้งค่าเป็น bind mount ไม่ใช่ named volume โดยมีจุดประสงค์เพื่อให้คุณสามารถระบุ path สำหรับเครื่องมือสำรองข้อมูลได้โดยตรง ซึ่งมีประโยชน์มากกว่าการจัดการแบบ volume ปกติ ให้สร้างไดเรกทอรีดังกล่าวด้วย UID www-data ของ image และกำหนดสิทธิ์การเข้าถึงตามที่ Nextcloud ต้องการ:

sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/data

โปรดสังเกตการเปิดพอร์ต: 127.0.0.1:8080:80 Docker จะเปิดพอร์ตโดยการเขียนกฎ DNAT ซึ่งจะถูกประเมินก่อนที่ chain INPUT ของ ufw จะได้รับแพ็กเก็ต ดังนั้นการใช้ 8080:80 เปล่าๆ จะทำให้ Nextcloud ที่ไม่มีการเข้ารหัสถูกเปิดเผยสู่สาธารณะโดยไม่สนใจกฎของ ufw การผูกพอร์ตไว้กับ loopback จะช่วยป้องกันไม่ให้เข้าถึงได้จากอินเทอร์เฟซสาธารณะ จากนั้นไฟร์วอลล์เพียงแค่ต้องอนุญาตการเชื่อมต่อจาก proxy เท่านั้น และหากคุณไม่ต้องการเปิดพอร์ต SSH ให้เข้าถึงได้จากอินเทอร์เน็ตทั่วไป การเข้าถึง VPS ผ่าน WireGuard VPN ที่คุณโฮสต์เอง จะช่วยให้คุณสามารถปิดพอร์ต 22 จากกฎสาธารณะได้ทั้งหมด:

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 ในการบูตครั้งแรก ระบบจะคัดลอกโครงสร้างแอปพลิเคชันทั้งหมดลงใน volume และรันตัวติดตั้ง คอนเทนเนอร์จะไม่ตอบสนองใดๆ จนกว่ากระบวนการดังกล่าวจะเสร็จสิ้น

TLS และ reverse proxy

ติดตั้ง nginx และ certbot จาก repository ของ distribution สร้าง server block สำหรับพอร์ต 80 ตามปกติด้วย server_name ที่ถูกต้อง จากนั้นให้ certbot ทำการเขียนทับไฟล์ config กลไกของ HTTP-01 challenge, ตัวตั้งเวลาต่ออายุ และรูปแบบความล้มเหลวต่างๆ ได้อธิบายไว้อย่างละเอียดใน การออกใบรับรอง Let's Encrypt ด้วย certbot และ nginx บน Ubuntu 24.04:

sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.com

Certbot จะเพิ่มบรรทัด ssl_certificate และการเปลี่ยนเส้นทาง :80:443 พร้อมทั้งติดตั้ง systemd timer เพื่อต่ออายุใบรับรองที่มีอายุ 90 วัน ตรวจสอบว่า timer ดังกล่าวมีอยู่จริงด้วย systemctl list-timers | grep certbot เพราะหากตัวตั้งเวลาต่ออายุไม่ได้ถูกเปิดใช้งาน ใบรับรองนั้นก็เปรียบเสมือนระเบิดเวลา 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 มาพร้อมกับ build ที่เก่ากว่าซึ่งใช้ listen 443 ssl http2; แทน คุณสามารถใช้ nginx -t เพื่อตรวจสอบว่า build ของคุณรองรับคำสั่งใด

client_max_body_size และการตั้งค่า timeout ที่ยาวขึ้นจะช่วยป้องกันไม่ให้การอัปโหลดไฟล์ขนาดใหญ่ถูกตัดการเชื่อมต่อกลางคัน ส่วน proxy_request_buffering off จะช่วยให้การอัปโหลดข้อมูลไหลผ่านไปยังปลายทางโดยตรง แทนที่จะต้องเขียนไฟล์ทั้งหมดลงบนดิสก์ของ proxy ก่อน

nginx บนโฮสต์เป็นวิธีที่ง่ายที่สุดสำหรับแอปพลิเคชันเดียว หาก Nextcloud ต้องใช้งานร่วมกับ container อื่นบน VPS เดียวกัน การใช้ การรัน Traefik ในฐานะ Docker Compose reverse proxy สำหรับหลายแอปพลิเคชัน จะช่วยย้ายการจัดการเส้นทางและการออกใบรับรองไปไว้ใน container labels แทน โดยที่ข้อควรพิจารณาเรื่อง client_max_body_size และการตั้งค่า timeout ยังคงต้องนำมาปรับใช้ในรูปแบบของ middleware และ transport settings เช่นเดิม

trusted_proxies และ overwriteprotocol

นี่คือจุดที่การติดตั้ง Nextcloud แบบ self-hosted ส่วนใหญ่ทำผิดพลาด และอาการที่เกิดขึ้นมักดูไม่เกี่ยวข้องกับสาเหตุที่แท้จริง

X-Forwarded-Proto: https จะถูกนำมาพิจารณาก็ต่อเมื่อคำขอมาจากที่อยู่ที่ระบุไว้ใน trusted_proxies เท่านั้น หากไม่เป็นไปตามเงื่อนไข Nextcloud จะเข้าใจว่าคำขอนั้นเป็น HTTP ธรรมดาและสร้าง URL แบบ http:// ออกมา จากนั้น proxy จะสั่ง redirect ไปยัง HTTPS; เบราว์เซอร์จะทำตาม; แล้ว Nextcloud ก็จะสร้าง http:// ออกมาอีกครั้ง ซึ่งนี่คือสาเหตุของปัญหา redirect loop ส่วน OVERWRITEPROTOCOL: https จะเป็นการบังคับใช้ scheme โดยไม่สนใจเงื่อนไขอื่น

กับดักใน TRUSTED_PROXIES คือที่อยู่ที่ Nextcloud มองเห็นนั้น ไม่ใช่ 127.0.0.1 เนื่องจาก nginx ทำงานบนโฮสต์และเชื่อมต่อไปยังพอร์ตที่เปิดไว้ 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 หากตั้งค่ากว้างเกินไป ไคลเอนต์ใดๆ ก็อาจปลอมแปลง X-Forwarded-For ได้ แต่หากตั้งค่าผิดพลาด การล็อกอินทุกครั้งจะดูเหมือนมาจากที่อยู่ของ gateway ทำให้ระบบป้องกัน brute-force บล็อกทั้งอินสแตนซ์ของคุณในคราวเดียว และหน้าสรุปสำหรับผู้ดูแลระบบจะแสดงข้อความว่า "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy."

OVERWRITECLIURL มีความสำคัญสำหรับ cron container ซึ่งไม่มีคำขอขาเข้าให้ใช้ระบุชื่อโฮสต์ หากไม่มีการตั้งค่านี้ งานเบื้องหลัง (background jobs) จะสร้างลิงก์ไปยัง localhost และการแจ้งเตือนทางอีเมลจะส่ง URL ที่ใช้งานไม่ได้ไปให้

งานเบื้องหลัง: ใช้ cron แทน AJAX

ตัวรันงานเริ่มต้นของ Nextcloud คือ AJAX ซึ่งงานจะถูกประมวลผลก็ต่อเมื่อมีผู้ใช้งานโหลดหน้าเว็บเท่านั้น เนื่องจากไม่มีใครเข้าใช้งานระบบในช่วงเวลา 04:00 น. งานอย่างการลบไฟล์ขยะ, การล้างข้อมูลเวอร์ชันเก่า, การสร้างรูปตัวอย่าง (previews) และการลองส่งข้อมูลแบบ federated ใหม่จึงหยุดชะงัก ส่งผลให้เกิดอาการไดเรกทอรีข้อมูลมีขนาดใหญ่ขึ้นเรื่อยๆ บริการ cron ด้านบนจะรันลูป /cron.sh อย่างเป็นทางการโดยใช้โวลุ่มเดียวกัน คุณต้องตั้งค่าให้ 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

A filesystem-only backup restores to a broken instance. The data directory holds the bytes; Postgres holds the file cache, shares, users, and app state; config.php holds the database credentials, the instance ID and the password salt. Restore the files without the database and Nextcloud cannot see them. Restore the database without config.php and it cannot open the database. Restore an old database against a newer data directory and you get shares pointing at files that moved.

Back up all three, from a quiesced instance:

#!/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 is what makes the dump and the file copy agree with each other. Skip it and you will eventually capture a database that references a file the rsync had not reached yet. Note that the script keeps timestamped database dumps but only one rolling mirror of the data directory, rsync --delete overwrites it each run, so only the newest dump pairs with the file copy.

Then get it off the box. A backup that lives on the same VPS as the thing it backs up is a copy, not a backup. restic against object storage or a second host is the usual answer, and its deduplication handles the data directory far better than a nightly tarball. The full setup, from repository init to the nightly timer and the restore drill, is in off-box VPS backups with restic.

Restore is not simply the reverse. A freshly-started stack runs the installer and writes a brand-new config.php, a new instance ID and password salt, and importing the dump on top of that new identity leaves broken sessions and share tokens. Put the old identity back first, in this order:

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 --all

files:scan reconciles the file cache with what is actually on disk. Rehearse this once, on a spare VPS, before you need it. The same split between bytes on disk and metadata in Postgres governs every other app of this shape, which is why an Immich backup that captures the library but not the database restores to an empty timeline.

การอัปเกรด: ทีละหนึ่งเวอร์ชันหลัก

Nextcloud รองรับการอัปเกรดทีละหนึ่งเวอร์ชันหลักเท่านั้น การข้ามจาก 29 ไป 31 จะไม่ทำงานอย่างราบรื่น แต่จะเกิดข้อผิดพลาด Exception: Updates between multiple major versions and downgrades are unsupported. และค้างอยู่ในโหมดซ่อมบำรุง (maintenance mode)

ขั้นตอนการอัปเกรดผ่าน Docker คือ: สำรองข้อมูล, แก้ไข tag จาก 31 เป็น 32 ในทั้งบริการ app และ cron จากนั้นใช้คำสั่ง docker compose pull && docker compose up -d ตามด้วย docker compose logs -f app ตัว entrypoint ของ image จะตรวจพบโค้ดเวอร์ชันใหม่เทียบกับข้อมูลเดิมและรัน occ upgrade โดยอัตโนมัติ ห้ามขัดจังหวะกระบวนการนี้ เมื่อ log นิ่งแล้ว ให้รัน docker compose exec -u www-data app php occ status และตรวจสอบ versionstring รวมถึงตรวจสอบว่าแอปพลิเคชันต่างๆ กลับมาเปิดใช้งานตามปกติ

กฎสองข้อที่จะช่วยคุณได้คือ: อัปเกรดทีละหนึ่งเวอร์ชันหลัก ตรวจสอบความเรียบร้อย แล้วจึงอัปเกรดเวอร์ชันถัดไป และห้ามแก้ไข tag ในบริการ app โดยไม่แก้ไข cron ให้ตรงกัน เพราะการใช้ Nextcloud สองเวอร์ชันที่แตกต่างกันกับฐานข้อมูลชุดเดียวกันจะทำให้ข้อมูลเสียหาย

ข้อผิดพลาดที่คุณจะพบจริง

"Your data directory is readable by other users. Please change the permissions to 0770." ไดเรกทอรีที่ทำ bind-mount มีสิทธิ์อ่านสำหรับกลุ่มหรือบุคคลทั่วไป ให้แก้ไขสิทธิ์ด้วย 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 ไม่เคยถูกเริ่มต้นใช้งาน, มีการพิมพ์ path ผิด หรือมีการสลับไดเรกทอรีเปล่าเข้ามาแทนที่ instance ที่ใช้งานอยู่เดิม ให้ตรวจสอบว่า path บน host ตรงกับบรรทัด volume ที่กำหนดไว้

"Access through untrusted domain." ชื่อโฮสต์ในคำขอไม่ได้อยู่ใน 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 กำลังเริ่มต้น (ตรวจสอบด้วย docker compose logs app), container หยุดทำงานไปแล้ว (docker compose ps) หรือบรรทัด publish ไม่ตรงกับพอร์ตของ proxy_pass ให้ยืนยันสถานะด้วย ss -ltnp | grep 8080

เกิดลูปการ redirect หรือคำเตือน "insecure" ในหน้า admin overview อาจเกิดจากการขาด OVERWRITEPROTOCOL: https หรือ TRUSTED_PROXIES ไม่ได้ระบุ subnet ของ Docker gateway ไว้ โปรดดูส่วนของ proxy ด้านบน

LockedException: "files/..." is locked หากตั้งค่า REDIS_HOST ไว้ ตัว image จะกำหนดให้ Redis เป็น locking backend ซึ่งจะช่วยลดปัญหา stale locks ได้ หากไม่มีการตั้งค่านี้ locks จะถูกเก็บไว้ในตารางฐานข้อมูล oc_file_locks และหากคำขอถูกยกเลิกกลางคันระหว่างเขียนข้อมูล จะทำให้มีแถวข้อมูลค้างอยู่ ให้ตรวจสอบว่ามีการใช้งาน Redis จริงหรือไม่ โดย occ config:system:get memcache.locking ควรแสดงผลเป็นคลาส Redis ก่อนที่คุณจะดำเนินการลบแถวข้อมูล lock ด้วยตนเอง

"The PHP memory limit is below the recommended value of 512MB." ให้เพิ่มค่า PHP_MEMORY_LIMIT แล้วสร้าง container ขึ้นมาใหม่ โปรดจำไว้ว่าการตั้งค่านี้ส่งผลต่อเพดานการใช้หน่วยความจำสูงสุดของระบบด้วย

สิ่งที่มักจะติดขัดเมื่อขยายขนาดระบบ

กำแพงด่านแรกคือไดเรกทอรีข้อมูลมีขนาดใหญ่เกินกว่าพื้นที่จัดเก็บข้อมูล (volume) การขยาย volume บน VPS ต้องทำทั้งการปรับขนาดพื้นที่และขยายระบบไฟล์ ซึ่งการวางแผนล่วงหน้าจะทำได้ง่ายกว่าการรอให้พื้นที่เต็ม 100% ดังนั้นควรตั้งค่าการแจ้งเตือนการใช้งานดิสก์ไว้ตั้งแต่ตอนนี้ ไม่ใช่รอให้ถึงเวลาวิกฤต

กำแพงด่านที่สองคือ oc_filecache การแสดงรายการไฟล์และการสแกนเพื่อซิงค์ข้อมูลจะช้าลงเมื่อจำนวนแถวในฐานข้อมูลเพิ่มขึ้น วิธีแก้ไขคือการจัดการฐานข้อมูล: เก็บ Postgres ไว้บนที่จัดเก็บข้อมูลที่รวดเร็ว จัดสรรหน่วยความจำ shared memory ให้เพียงพอ และลบไฟล์ขยะหรือเวอร์ชันเก่าออกด้วยการตั้งค่า retention แทนที่จะปล่อยให้สะสมไปเรื่อยๆ

ด่านที่สามคือการสร้างภาพตัวอย่าง (preview generation) ที่แย่งทรัพยากรจากงานส่วนอื่น บนเซิร์ฟเวอร์ขนาดเล็กควรจำกัดผู้ให้บริการ preview ให้เหลือน้อยที่สุด และห้ามรัน occ preview:generate-all ในช่วงเวลาทำงาน หากข้อมูลส่วนใหญ่ที่คุณจัดเก็บคือรูปภาพจากกล้องโทรศัพท์ งานสร้างภาพ thumbnail เหล่านี้ควรย้ายไปทำบนเซิร์ฟเวอร์สำหรับจัดการรูปภาพโดยเฉพาะแทน และ การเปรียบเทียบ PhotoPrism และ Immich ในด้านการใช้ RAM, แอปมือถือ และคำสั่งสำรองข้อมูล จะช่วยให้เห็นต้นทุนของแต่ละตัวเมื่อเทียบกับการใช้ Nextcloud เพียงอย่างเดียว

นอกเหนือจากนั้น คำตอบที่ตรงไปตรงมาคือบริการเสริมเหล่านี้ต้องการเครื่องเซิร์ฟเวอร์แยกต่างหาก Collabora และการค้นหาข้อความแบบเต็ม (full-text search) เป็นบริการที่ทำงานอยู่เบื้องหลังตลอดเวลาและมีโปรไฟล์การใช้หน่วยความจำของตัวเอง การนำบริการเหล่านี้ไปไว้บนเครื่องเดียวกับที่เก็บไฟล์ชุดเดียวของคุณจะทำให้ขอบเขตความเสียหาย (failure domain) กว้างขึ้นโดยไม่ได้รับประโยชน์ใดๆ หากคุณต้องการฟีเจอร์แก้ไขเอกสารในเบราว์เซอร์ ข้อจำกัดด้าน RAM และจำนวนการเชื่อมต่อที่แยก OnlyOffice ออกจาก Collabora จะเป็นตัวตัดสินว่า VPS ขนาด 2 ถึง 4 GB จะรองรับตัวไหนได้บ้าง ให้ย้ายการจัดเก็บไฟล์ไปไว้บน S3-compatible primary storage เมื่อ volume เริ่มไม่ตอบโจทย์ และโปรดทราบว่าวิธีนี้จะทำให้การสำรองข้อมูลทำได้ยากขึ้น ไม่ใช่ง่ายขึ้น เพราะฐานข้อมูลยังคงเก็บ metadata อยู่ และต้องถูก dump ข้อมูลให้สอดคล้องกับ bucket เสมอ

เมื่อ instance เริ่มให้บริการผู้ใช้งานจริง ให้ติดตั้ง Uptime Kuma ไว้หน้าบริการ เพื่อให้คุณทราบถึงปัญหา downtime ก่อนที่ไคลเอนต์ซิงค์ข้อมูลจะแจ้งเตือน Private cloud ทำงานร่วมกับ เมลเซิร์ฟเวอร์ของคุณเอง ได้เป็นอย่างดี และหากคุณไม่ต้องการเชื่อมต่อบริการต่างๆ ด้วยตัวเอง Cloudron, CasaOS และ Coolify จะเปรียบเทียบแพลตฟอร์มที่ช่วยจัดการงานเหล่านี้ให้คุณ หากเป้าหมายถัดไปของคุณคือการทำ search engine แบบ self-hosted ให้เตรียมรับมือกับปัญหาอีกระดับที่ต่างจากข้างต้น: ข้อผิดพลาด 429 ของ SearXNG อาจเกิดจากตัวจำกัดอัตราการใช้งาน (rate limiter) ของตัวมันเอง หรือเกิดจาก upstream engine บล็อก IP ของ VPS คุณ ซึ่งมีเพียง log เท่านั้นที่จะบอกได้ว่าสาเหตุมาจากอะไร

FAQ

ฉันสามารถรัน Nextcloud บน SQLite แทน Postgres ได้หรือไม่?

คุณสามารถทำได้และอิมเมจอย่างเป็นทางการก็รองรับ แต่ไคลเอนต์ซิงค์บนเดสก์ท็อปเพียงเครื่องเดียวที่ส่งคำขอแบบขนานจะทำให้เกิด SQLSTATE[HY000]: General error: 5 database is locked และข้อผิดพลาด HTTP 500 เนื่องจาก SQLite จะล็อกฐานข้อมูลทั้งหมดสำหรับการเขียน และ Nextcloud มีการเขียนข้อมูลตลอดเวลา ทั้งการล็อกไฟล์, แถวบันทึกกิจกรรม และสถานะงาน ควรเริ่มต้นด้วย Postgres หรือ MariaDB; แม้จะมี occ db:convert-type ให้ใช้งาน แต่การย้ายข้อมูลในขณะที่ระบบยังทำงานอยู่เป็นกระบวนการที่ยาวนานและมีความเสี่ยงสูง

Nextcloud VPS ต้องการ RAM จริงๆ เท่าไหร่?

ให้คำนวณจากจำนวนการใช้งานพร้อมกัน (concurrency) ไม่ใช่จำนวนผู้ใช้ หน่วยความจำที่ใช้จริงในกรณีที่แย่ที่สุดคือจำนวนคำขอพร้อมกันคูณด้วย PHP_MEMORY_LIMIT บวกกับ shared buffers ของ Postgres และ backend หนึ่งตัวต่อการเชื่อมต่อ รวมถึงส่วนที่พุ่งสูงขึ้นจากการสร้างตัวอย่างภาพ (preview) เซิร์ฟเวอร์ขนาด 2 GB สามารถรันอินสแตนซ์สำหรับครอบครัวขนาดเล็กได้หากคุณจำกัดการสร้าง 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 บนคอนเทนเนอร์แอปให้สอดคล้องกัน

ทำไม Nextcloud ถึงเกิดการ redirect วนลูปหรือแจ้งเตือนเกี่ยวกับ reverse proxy?

คอนเทนเนอร์มองไม่เห็น nginx ที่ 127.0.0.1 แต่จะเห็น Docker bridge gateway ซึ่งอยู่ในช่วง 172.x เมื่อที่อยู่ดังกล่าวไม่ได้ถูกระบุใน TRUSTED_PROXIES ส่วนหัว X-Forwarded-Proto: https จะถูกละเลย Nextcloud จึงสร้าง URL แบบ http:// และ proxy จะส่งคำขอกลับไปกลับมา ให้ตั้งค่า TRUSTED_PROXIES เป็น bridge subnet ที่แท้จริงและกำหนด OVERWRITEPROTOCOL: https ให้คงที่

ฉันสามารถอัปเกรด Nextcloud จากเวอร์ชัน 29 ไปเป็น 31 โดยตรงได้หรือไม่?

ไม่ได้ Nextcloud รองรับการอัปเกรดข้ามเวอร์ชันหลักได้ทีละหนึ่งเวอร์ชันเท่านั้น การข้ามเวอร์ชันจะทำให้เกิดข้อผิดพลาด Updates between multiple major versions and downgrades are unsupported. และทิ้งอินสแตนซ์ไว้ในโหมดซ่อมบำรุง (maintenance mode) ให้สำรองข้อมูล ปรับเวอร์ชันขึ้นทีละหนึ่งเวอร์ชันหลักทั้งในบริการ app และ cron จากนั้นรัน docker compose pull && docker compose up -d ตรวจสอบความถูกต้องด้วย occ status แล้วจึงทำซ้ำขั้นตอนเดิมสำหรับเวอร์ชันถัดไป