SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

Docker로 VPS에 Nextcloud 구축 및 백업 가이드

Docker Compose와 Postgres, Redis를 사용하여 VPS에 Nextcloud를 직접 설치하는 방법을 설명합니다. TLS 설정부터 데이터 손실을 방지하는 일관된 백업 전략 및 업그레이드 프로세스까지 운영에 필요한 핵심 가이드를 제공합니다.

실제로 구축하게 될 시스템

이 가이드는 Docker Compose를 사용하여 VPS에서 Nextcloud를 실행하고, 그 앞에 Let's Encrypt TLS를 배치하며, 실제로 복구가 가능한 백업을 설정합니다. 4개의 컨테이너와 프록시로 구성됩니다. 루프백에서 대기하는 공식 nextcloud 이미지, 모든 파일 메타데이터를 저장하는 Postgres, 파일 잠금을 관리하는 Redis, cron 루프만 실행하는 두 번째 Nextcloud 이미지, 그리고 이들 앞단에서 TLS를 종료하는 호스트의 nginx입니다. 설치 자체는 20분이면 끝나며, 중요한 것은 설치 과정이 아닙니다. 첫 한 시간 동안 내리는 두 가지 결정이 1년 뒤에도 파일을 안전하게 보관할 수 있을지를 결정합니다. SQLite 대신 실제 데이터베이스를 사용하는 것, 그리고 데이터 디렉터리, 데이터베이스, config.php을 하나의 일관된 세트로 묶어 백업하는 것입니다.

이 가이드는 Ubuntu 24.04 LTS 또는 Debian 13 환경을 가정하며, Docker 공식 저장소에서 설치한 Compose v2 플러그인이 포함된 Docker Engine이 필요합니다. 또한 DNS A 레코드(IPv6를 사용하는 경우 AAAA 포함)가 이미 cloud.example.com을 VPS로 가리키고 있어야 합니다. 이 모든 과정은 사용자가 직접 제어하는 서버가 필요합니다. 타인의 SaaS 환경에서는 TLS 종료나 데이터베이스 덤프를 수행할 방법이 없기 때문입니다.

메모리 사용량 산정: 실제로 메모리를 소비하는 요소

Nextcloud의 메모리 사용량은 크게 세 가지 요소에 의해 결정되며, 이 중 어느 것도 'Nextcloud' 그 자체는 아닙니다.

PHP 워커. -apache 이미지는 PHP 인터프리터를 포함하는 워커 프로세스를 통해 각 동시 요청을 처리합니다. 각 워커는 PHP가 요청을 종료하기 전까지 PHP_MEMORY_LIMIT까지 메모리를 점유할 수 있습니다. 최악의 경우 상주 메모리 사용량은 대략 동시 요청 수 × 메모리 제한이 되며, 데스크톱 동기화 클라이언트는 사용자당 여러 개의 병렬 연결을 엽니다. 따라서 메모리 상한을 결정하는 것은 사용자 수가 아니라 동시성입니다.

데이터베이스. Postgres는 연결당 백엔드 프로세스를 포크(fork)하고 공유 버퍼를 상주 메모리에 유지합니다. 작업 세트 크기는 데이터의 바이트 수가 아니라 파일 수에 비례합니다. oc_filecache은 사용자별 파일당 하나의 행을 가집니다. 10만 개의 작은 파일은 100개의 큰 파일보다 데이터베이스에 더 큰 부하를 줍니다.

미리보기 생성. 썸네일을 생성할 때 원본 이미지를 전체 해상도로 메모리에 디코딩합니다. 동영상 미리보기는 ffmpeg을 호출합니다. occ preview:generate-all을 실행하면 이러한 작업이 연속적으로 반복되며, 이는 소규모 VPS가 OOM killer에 의해 프로세스가 종료되는 가장 흔한 원인입니다.

Redis는 상대적으로 메모리 점유가 적습니다. 나중에 추가하는 Collabora, 전체 텍스트 검색, 백신 스캐너와 같은 기능은 각각 고유한 메모리 점유율을 가진 별도의 상주 서비스이므로, 활성화하기 전에 미리 산정 계획에 포함해야 합니다.

RAM이 부족할 경우 조정할 수 있는 설정은 다음과 같습니다. PHP_MEMORY_LIMIT를 낮추고, preview_max_x / preview_max_y / preview_max_filesize_image를 제한하며, enabledPreviewProviders을 실제로 탐색하는 형식으로만 다듬으십시오. 또한 trashbin_retention_obligationversions_retention_obligation를 설정하여 데이터 디렉터리가 파일 크기의 몇 배로 조용히 증가하지 않도록 하십시오. 스왑 파일을 추가하는 것도 방법입니다. 스왑은 느리지만, 업그레이드 도중 OOM kill이 발생하는 것보다는 낫습니다.

SQLite가 문제를 일으키는 이유

Nextcloud는 SQLite 지원을 포함하고 있으며 공식 이미지도 이를 기본적으로 사용합니다. 하지만 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:

메이저 태그를 고정하고, 31을 그대로 복사하기 전에 Docker Hub에서 현재 버전을 확인하십시오. latest를 사용하면 향후 docker compose pull 업데이트 시 메이저 버전이 변경될 수 있으며, Nextcloud는 이를 지원하지 않습니다.

데이터 디렉터리는 깔끔함보다 백업 도구가 직접 접근할 수 있는 경로가 더 중요하므로, 명명된 볼륨이 아닌 바인드 마운트를 사용합니다. 이미지의 www-data UID를 사용하여 디렉터리를 생성하고 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는 ufw의 INPUT 체인이 패킷을 확인하기 전에 DNAT 규칙을 작성하여 포트를 게시하므로, 단순히 8080:80만 설정하면 ufw 설정과 관계없이 암호화되지 않은 Nextcloud가 공개 인터넷에 노출됩니다. 루프백 인터페이스에 바인딩하면 공개 인터페이스로 노출되지 않습니다. 이후 방화벽은 프록시만 허용하면 되며, SSH를 인터넷 전체에 개방하고 싶지 않다면 자체 호스팅 WireGuard VPN을 통해 VPS에 접속하여 포트 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로 로그를 확인하십시오. 첫 부팅 시 전체 애플리케이션 트리가 볼륨으로 복사되고 설치 프로그램이 실행되므로, 해당 작업이 완료될 때까지 컨테이너는 응답하지 않습니다.

TLS와 리버스 프록시

배포판 저장소에서 nginx와 certbot을 설치하고, 올바른 server_name을 포함한 기본 80번 포트 서버 블록을 생성한 뒤 certbot이 이를 재작성하도록 합니다. HTTP-01 챌린지의 작동 원리, 갱신 타이머, 실패 유형에 관한 자세한 내용은 Ubuntu 24.04에서 certbot과 nginx로 Let's Encrypt 인증서 발급하기에서 확인할 수 있습니다.

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

Certbot은 ssl_certificate 라인과 :80:443 리다이렉트를 추가하고, 90일 주기로 인증서를 갱신하는 systemd 타이머를 설치합니다. systemctl list-timers | grep certbot 명령으로 타이머가 존재하는지 확인하십시오. 갱신 타이머가 활성화되지 않았다면 90일짜리 시한폭탄과 다름없습니다.

프록시 블록 설정은 다음과 같습니다.

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과 긴 읽기 타임아웃 설정은 대용량 업로드가 중간에 끊기는 것을 방지합니다. proxy_request_buffering off는 전체 파일을 프록시 디스크에 먼저 저장하지 않고 업로드 스트림을 그대로 전달합니다.

호스트에서 실행되는 nginx는 단일 애플리케이션을 운영할 때 가장 단순하고 효과적인 방법입니다. 만약 Nextcloud를 다른 컨테이너와 함께 VPS에서 운영해야 한다면, 여러 애플리케이션을 위한 Docker Compose 리버스 프록시로 Traefik 실행하기를 참고하여 라우팅과 인증서 발급을 컨테이너 레이블로 관리하십시오. 이때도 동일한 client_max_body_size 및 타임아웃 관련 설정이 미들웨어와 전송 설정의 형태로 다시 등장합니다.

trusted_proxies 및 overwriteprotocol

대부분의 자가 호스팅 Nextcloud 인스턴스에서 문제가 발생하는 지점이며, 증상은 원인과 무관해 보이는 경우가 많습니다.

X-Forwarded-Proto: https은 요청이 trusted_proxies에 나열된 주소에서 도착할 때만 적용됩니다. 이 설정이 적용되지 않으면 Nextcloud는 요청을 일반 HTTP로 간주하여 http:// URL을 생성합니다. 프록시는 이를 HTTPS로 리다이렉트하고, 브라우저는 이를 따르며, Nextcloud는 다시 http://를 생성합니다. 이것이 리다이렉트 루프의 원인입니다. OVERWRITEPROTOCOL: https는 요청의 스킴(scheme)을 무조건 고정합니다.

TRUSTED_PROXIES 설정 시 주의할 점은 Nextcloud가 인식하는 주소가 127.0.0.1이 아니라는 것입니다. nginx는 호스트에서 실행되어 게시된 포트로 연결되므로, 컨테이너는 172.x 대역에 있는 Docker 브리지 게이트웨이 주소를 보게 됩니다. 실제 서브넷을 확인하십시오.

docker network inspect nextcloud_default \
  -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'

해당 CIDR(또는 이를 포함하는 172.16.0.0/12)을 TRUSTED_PROXIES에 입력하십시오. 범위를 너무 넓게 설정하면 모든 클라이언트가 X-Forwarded-For을 스푸핑할 수 있습니다. 잘못 설정하면 모든 로그인 시도가 게이트웨이 주소에서 오는 것으로 간주되어 무차별 대입 공격 방지 기능이 인스턴스 전체를 즉시 차단하며, 관리자 개요 페이지에 "리버스 프록시 헤더 설정이 올바르지 않거나, 신뢰할 수 없는 프록시를 통해 Nextcloud에 접근하고 있습니다."라는 경고가 표시됩니다.

OVERWRITECLIURL는 cron 컨테이너에 중요합니다. cron 컨테이너는 호스트 이름을 유추할 수 있는 들어오는 요청이 없기 때문입니다. 이 설정이 없으면 백그라운드 작업이 localhost에 대한 링크를 생성하고, 이메일 알림에는 사용할 수 없는 URL이 포함됩니다.

백그라운드 작업: AJAX 대신 cron 사용

Nextcloud의 기본 작업 실행 방식은 AJAX입니다. 즉, 사용자가 페이지를 불러올 때 부수 효과로 작업이 실행됩니다. 04:00에는 아무도 브라우징을 하지 않으므로 휴지통 비우기, 버전 정리, 미리보기 생성, 연합(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)을 설정해 두는 것이 좋습니다.

백업: 세 가지 요소, 혹은 전무

파일 시스템만 백업하면 인스턴스가 손상되었을 때 복구할 수 없습니다. 데이터 디렉터리에는 바이트 단위의 데이터가 저장되고, Postgres에는 파일 캐시, 공유, 사용자, 애플리케이션 상태가 저장되며, config.php에는 데이터베이스 자격 증명, 인스턴스 ID, 비밀번호 솔트가 저장됩니다. 데이터베이스 없이 파일만 복구하면 Nextcloud는 해당 파일을 인식하지 못합니다. config.php 없이 데이터베이스만 복구하면 데이터베이스를 열 수 없습니다. 구버전 데이터베이스를 최신 데이터 디렉터리에 복구하면 이동된 파일을 가리키는 공유 정보가 꼬이게 됩니다.

인스턴스를 정지(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)는 덤프와 파일 복사본 간의 일관성을 유지하는 핵심입니다. 이 과정을 생략하면 rsync가 도달하지 못한 파일을 참조하는 데이터베이스 상태가 백업될 수 있습니다. 스크립트는 타임스탬프가 찍힌 데이터베이스 덤프를 보관하지만 데이터 디렉터리는 하나의 롤링 미러로만 유지한다는 점에 유의하십시오. rsync --delete은 실행될 때마다 이전 파일을 덮어쓰므로, 가장 최신 덤프만이 파일 복사본과 짝을 이룹니다.

그다음 백업본을 서버 외부로 전송하십시오. 백업 대상과 동일한 VPS에 저장된 백업은 백업이 아니라 단순 복사본일 뿐입니다. 객체 스토리지나 별도의 호스트를 대상으로 하는 restic이 일반적인 해결책이며, 이 방식의 중복 제거 기능은 매일 생성하는 tarball보다 데이터 디렉터리를 훨씬 효율적으로 관리합니다. 리포지토리 초기화부터 매일 실행되는 타이머, 복구 훈련까지 전체 설정 과정은 restic을 이용한 외부 VPS 백업에서 확인할 수 있습니다.

복구는 단순히 백업의 역순이 아닙니다. 새로 시작된 스택은 설치 프로그램을 실행하여 완전히 새로운 config.php, 새로운 인스턴스 ID, 새로운 비밀번호 솔트를 생성합니다. 이 새로운 식별자 위에 덤프를 가져오면 세션과 공유 토큰이 모두 깨집니다. 반드시 이전 식별자를 먼저 복원해야 하며, 순서는 다음과 같습니다:

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은 파일 캐시와 실제 디스크상의 데이터를 대조하여 동기화합니다. 실제 상황이 닥치기 전에 여분의 VPS에서 이 과정을 한 번 연습해 보십시오. 디스크상의 바이트와 Postgres의 메타데이터를 분리하여 관리하는 방식은 이와 유사한 구조를 가진 모든 애플리케이션에 동일하게 적용됩니다. 이것이 바로 라이브러리는 백업했지만 데이터베이스를 백업하지 않은 Immich가 빈 타임라인으로 복구되는 이유입니다.

업그레이드: 한 번에 하나의 메이저 버전만

Nextcloud는 한 번에 정확히 하나의 메이저 버전만 업그레이드할 수 있습니다. 29에서 31로 바로 건너뛰면 정상적으로 처리되지 않고 Exception: Updates between multiple major versions and downgrades are unsupported. 오류가 발생하며 유지보수 모드에 빠지게 됩니다.

Docker 업그레이드 절차는 다음과 같습니다. 먼저 백업을 수행하고, appcron 서비스 모두에서 태그를 31에서 32으로 수정합니다. 그 후 docker compose pull && docker compose up -d를 실행하고 docker compose logs -f app을 수행합니다. 이미지의 entrypoint가 기존 데이터와 비교하여 더 최신 코드를 감지하면 자동으로 occ upgrade을 실행합니다. 이 과정을 중단하지 마십시오. 로그 출력이 멈추면 docker compose exec -u www-data app php occ status를 실행하고 versionstring을 확인하여 앱이 다시 활성화되었는지 점검하십시오.

문제를 방지하는 두 가지 규칙이 있습니다. 첫째, 메이저 버전을 하나씩 올리고 검증한 뒤 다음 버전으로 넘어가십시오. 둘째, cron를 일치시키지 않은 채 app 서비스의 태그를 수정하지 마십시오. 서로 다른 두 개의 Nextcloud 버전이 하나의 데이터베이스를 참조하면 데이터가 손상될 수 있습니다.

실제로 마주하게 될 오류들

"Your data directory is readable by other users. Please change the permissions to 0770." 바인드 마운트된 디렉터리에 그룹 또는 기타 사용자의 읽기 권한이 설정되어 있습니다. sudo chmod 0770 /srv/nextcloud/datasudo chown -R 33:33 /srv/nextcloud/data을 확인하십시오.

"Your data directory is invalid. Ensure there is a file called .ocdata in the root." 바인드 마운트 경로가 Nextcloud가 초기화되지 않은 곳을 가리키거나, 경로에 오타가 있거나, 정상 작동 중인 인스턴스 아래에 비어 있는 새 디렉터리가 교체된 경우입니다. 호스트 경로가 볼륨 설정 라인과 일치하는지 확인하십시오.

"Access through untrusted domain." 요청에 사용된 호스트 이름이 trusted_domains에 없습니다. NEXTCLOUD_TRUSTED_DOMAINS는 최초 설치 시에만 적용됩니다. 설치 이후에는 occ config:system:set trusted_domains 1 --value=cloud.example.com을 통해 실시간으로 설정하십시오.

502 Bad Gateway, /var/log/nginx/error.logconnect() failed (111: Connection refused) while connecting to upstream 오류가 기록됨. nginx가 127.0.0.1:8080에서 아무런 응답을 받지 못했습니다. 컨테이너가 아직 초기화 중이거나(docker compose logs app 확인), 종료되었거나(docker compose ps), 또는 publish 설정이 proxy_pass 포트와 일치하지 않는 경우입니다. ss -ltnp | grep 8080으로 확인하십시오.

리다이렉트 루프 또는 관리자 개요의 "insecure" 경고. OVERWRITEPROTOCOL: https이 누락되었거나, TRUSTED_PROXIES에 Docker 게이트웨이 서브넷이 포함되지 않았습니다. 위의 프록시 섹션을 참조하십시오.

LockedException: "files/..." is locked. REDIS_HOST을 설정하면 이미지가 Redis를 잠금 백엔드로 구성하므로 오래된 잠금(stale locks)이 발생하는 경우가 드뭅니다. 설정하지 않으면 잠금 정보가 데이터베이스 테이블 oc_file_locks에 저장되며, 쓰기 도중 요청이 중단되면 행이 남게 됩니다. 수동으로 잠금 행을 삭제하기 전에 occ config:system:get memcache.locking을 실행하여 Redis 클래스가 반환되는지 확인하십시오.

"The PHP memory limit is below the recommended value of 512MB." PHP_MEMORY_LIMIT 값을 높이고 컨테이너를 다시 생성하십시오. 이 설정이 최악의 상황에서 메모리 사용량 상한에 어떤 영향을 미치는지 유의하십시오.

규모가 커질 때 발생하는 문제

첫 번째 장벽은 데이터 디렉터리가 볼륨 용량을 초과하는 것입니다. VPS에서 볼륨을 확장하려면 크기 조정과 파일 시스템 확장이 필요한데, 디스크가 100% 찼을 때보다 미리 계획하여 수행하는 것이 훨씬 수월합니다. 디스크 사용량 알림은 나중이 아니라 지금 설정하십시오.

두 번째 장벽은 oc_filecache입니다. 파일 목록 조회와 동기화 스캔은 행 수가 늘어날수록 느려집니다. 해결책은 데이터베이스 최적화입니다. Postgres를 빠른 스토리지에 유지하고 충분한 공유 메모리를 할당하십시오. 또한 불필요한 데이터와 버전은 보관 정책을 설정하여 정리하고, 무한정 쌓이지 않도록 관리해야 합니다.

세 번째는 미리 보기 생성 작업이 다른 서비스와 자원을 두고 경쟁하는 것입니다. 소규모 서버라면 미리 보기 제공자(preview providers)를 최소한으로 유지하고, 업무 시간 중에는 occ preview:generate-all을 실행하지 마십시오. 저장하는 데이터의 대부분이 스마트폰 사진이라면, 해당 썸네일 생성 작업은 전용 사진 서버에서 처리하는 것이 좋습니다. RAM, 모바일 앱, 백업 명령어를 기준으로 비교한 PhotoPrism과 Immich에서 Nextcloud 서버와 비교했을 때 각각 어떤 비용이 드는지 확인할 수 있습니다.

그 이상의 규모가 되면, 추가 기능들은 별도의 서버에서 운영하는 것이 정답입니다. Collabora와 전체 텍스트 검색은 고유한 메모리 프로필을 가진 별도의 상주 서비스입니다. 이 서비스들을 파일 원본이 저장된 서버에 함께 두면, 이점은 없으면서 장애 범위만 커집니다. 브라우저 내 문서 편집 기능이 필요하다면 OnlyOffice와 Collabora를 구분 짓는 벤더의 RAM 최소 사양 및 연결 제한을 참고하여 2~4 GB RAM의 VPS에서 운영 가능한 서비스를 선택하십시오. 볼륨 구성이 적절하지 않게 되면 파일 스토리지를 S3 호환 스토리지로 이전하십시오. 단, 이 경우 백업이 더 어려워진다는 점에 유의해야 합니다. 데이터베이스에는 여전히 메타데이터가 저장되므로, 버킷과 동기화하여 덤프를 생성해야 합니다.

인스턴스가 실제 사용자에게 서비스를 제공하기 시작하면, Uptime Kuma를 앞단에 배치하여 동기화 클라이언트보다 먼저 다운타임을 인지할 수 있도록 하십시오. 프라이빗 클라우드는 자체 메일 서버와 함께 운영하기 좋습니다. 서비스를 직접 연결하는 것이 번거롭다면 Cloudron, CasaOS, Coolify를 통해 이를 자동화해 주는 플랫폼들을 비교해 보십시오. 자체 호스팅 검색 엔진을 도입할 계획이라면 앞서 언급한 문제들과는 다른 유형의 문제를 예상해야 합니다. SearXNG의 429 에러는 자체 속도 제한(rate limiter)이나 업스트림 엔진이 VPS IP를 차단하여 발생하며, 로그를 확인해야만 정확한 원인을 알 수 있습니다.

FAQ

Postgres 대신 SQLite에서 Nextcloud를 실행할 수 있습니까?

실행할 수 있으며 공식 이미지도 이를 허용하지만, 데스크톱 동기화 클라이언트가 병렬 요청을 보내면 SQLSTATE[HY000]: General error: 5 database is locked 오류와 HTTP 500 응답이 발생합니다. SQLite는 데이터베이스 전체에 쓰기 잠금을 거는데, Nextcloud는 파일 잠금, 활동 기록, 작업 상태 등 지속적으로 쓰기 작업을 수행하기 때문입니다. 처음부터 Postgres나 MariaDB를 사용하십시오. occ db:convert-type 도구가 존재하지만, 이는 운영 중인 데이터에 대해 전체를 한꺼번에 마이그레이션해야 하는 부담이 있습니다.

Nextcloud VPS에는 실제로 얼마만큼의 RAM이 필요합니까?

사용자 수가 아닌 동시 접속 수를 기준으로 산정하십시오. 최악의 경우 상주 메모리 사용량은 대략 동시 요청 수에 PHP_MEMORY_LIMIT를 곱한 값에 Postgres 공유 버퍼, 연결당 백엔드 하나, 그리고 미리보기 생성 시 급증하는 메모리 사용량을 더한 값입니다. 미리보기 기능을 제한하고 스왑을 추가한다면 2 GB 서버로도 소규모 가정용 인스턴스를 운영할 수 있습니다. 하지만 Collabora나 전체 텍스트 검색 기능을 추가하면 별도의 상주 서비스들을 위한 메모리를 추가로 확보해야 합니다.

nginx 리버스 프록시 뒤에서 대용량 업로드가 실패하는 이유는 무엇입니까?

보통 프록시의 두 가지 설정 때문입니다. client_max_body_size이 기본값인 1 MB로 설정되어 있으면 요청이 잘리고, proxy_read_timeout / proxy_send_timeout 값이 짧으면 긴 전송 과정이 중간에 끊깁니다. 두 값을 넉넉하게 설정하고, proxy_request_buffering off을 스풀링 대신 스트리밍으로 변경하며, 애플리케이션 컨테이너의 PHP_UPLOAD_LIMIT 값을 그에 맞춰 높이십시오.

Nextcloud에서 리다이렉트 루프가 발생하거나 리버스 프록시 관련 경고가 뜨는 이유는 무엇입니까?

컨테이너는 127.0.0.1에 있는 nginx를 보지 못하고 172.x 대역에 있는 Docker 브리지 게이트웨이를 봅니다. 해당 주소가 TRUSTED_PROXIES에 누락되면 X-Forwarded-Proto: https 헤더가 무시되고, Nextcloud는 http:// URL을 생성하며, 프록시는 이를 다시 반환합니다. TRUSTED_PROXIES을 실제 브리지 서브넷으로 설정하고 OVERWRITEPROTOCOL: https을 고정하십시오.

Nextcloud를 29 버전에서 31 버전으로 바로 업그레이드할 수 있습니까?

아니요. Nextcloud는 한 번에 하나의 메이저 버전 업그레이드만 지원합니다. 버전을 건너뛰면 Updates between multiple major versions and downgrades are unsupported. 오류와 함께 인스턴스가 유지보수 모드에 멈추게 됩니다. 백업을 수행하고, appcron 서비스의 태그를 한 단계 메이저 버전으로 올린 뒤, docker compose pull && docker compose up -d를 실행하고 occ status으로 확인하는 과정을 반복하십시오.