SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor

Nextcloud en un VPS con Docker, TLS y copias de seguridad

Configura Nextcloud en un VPS con Docker Compose, Postgres, Redis y nginx, y aprende a respaldar y restaurar datos, base de datos y configuración de forma coherente.

Lo que realmente está configurando

Esta guía ejecuta Nextcloud en un VPS con Docker Compose, coloca TLS de Let's Encrypt delante y configura una copia de seguridad que se puede restaurar de verdad. Hay cuatro contenedores y un proxy: la imagen oficial nextcloud, que escucha en loopback; Postgres, que almacena todos los metadatos de los archivos; Redis, que almacena los bloqueos de archivos; una segunda copia de la imagen de Nextcloud, que ejecuta únicamente el bucle de cron; y nginx en el host, que termina TLS delante de todos ellos. La instalación tarda veinte minutos, pero no es la parte importante. Dos decisiones tomadas durante la primera hora determinan si sus archivos seguirán disponibles dentro de un año: usar una base de datos real en lugar de SQLite y realizar una copia de seguridad que capture el directorio de datos, la base de datos y config.php como un conjunto coherente.

Esto presupone Ubuntu 24.04 LTS o Debian 13, Docker Engine con el complemento Compose v2 instalado desde el repositorio oficial de Docker y un registro DNS A (además de AAAA si tiene IPv6) que ya apunte cloud.example.com al VPS. Todo requiere un servidor bajo su control. No hay forma de terminar TLS y volcar una base de datos en el SaaS de otra persona.

Dimensionamiento: qué consume realmente la memoria

El uso de memoria de Nextcloud está dominado por tres elementos, y ninguno de ellos es «Nextcloud» como tal.

Procesos de PHP. La imagen -apache atiende cada petición simultánea mediante un proceso de trabajo que contiene un intérprete de PHP. Cada proceso puede crecer hasta PHP_MEMORY_LIMIT antes de que PHP finalice la petición. La memoria residente máxima aproximada es peticiones simultáneas × límite de memoria, y un cliente de sincronización de escritorio abre varias conexiones paralelas por usuario. El límite lo establece la concurrencia, no el número de usuarios.

La base de datos. Postgres crea un proceso backend por conexión y mantiene los búferes compartidos en memoria residente. Su conjunto de trabajo aumenta según el número de archivos, no según el número de bytes: oc_filecache contiene una fila por archivo y por usuario. Cien mil archivos pequeños suponen una base de datos más exigente que cien archivos grandes.

Generación de vistas previas. La generación de una miniatura decodifica la imagen de origen en memoria con su resolución completa. Las vistas previas de vídeo ejecutan ffmpeg mediante un proceso externo. Ejecutar occ preview:generate-all repite ese pico varias veces seguidas y es la forma más habitual de provocar que un VPS pequeño active el OOM killer.

Redis consume relativamente pocos recursos. Cualquier componente que añada después, como Collabora, la búsqueda de texto completo o un antivirus, es un servicio residente independiente con su propia huella de memoria. Debe incluirlo en el plan de dimensionamiento antes de activarlo.

Si falta RAM, ajuste estos parámetros: reduzca PHP_MEMORY_LIMIT, limite preview_max_x / preview_max_y / preview_max_filesize_image, reduzca enabledPreviewProviders a los formatos que realmente consulta y configure trashbin_retention_obligation y versions_retention_obligation para que el directorio de datos no crezca silenciosamente hasta varias veces el tamaño de sus archivos. Añada un archivo de swap. El swap es lento, pero un OOM kill durante una actualización es peor.

Por qué SQLite falla

Nextcloud incluye compatibilidad con SQLite y la imagen oficial lo utilizará sin problemas. No lo haga. SQLite serializa las escrituras mediante un bloqueo de toda la base de datos: sólo puede escribir un proceso cada vez y el bloqueo se aplica a todo el archivo. Nextcloud escribe constantemente: bloqueos de archivos, registros de actividad, entradas de caché y estado de las tareas. Además, un solo cliente de escritorio que sincroniza un árbol de directorios genera muchas solicitudes en paralelo. Con ese patrón aparecen SQLSTATE[HY000]: General error: 5 database is locked y errores HTTP 500. El fallo se produce justo cuando la instancia empieza a ser útil.

Más adelante es posible convertir la base de datos mediante occ db:convert-type, pero se trata de una migración larga y atómica sobre un conjunto de datos activo. Empiece con Postgres o MariaDB.

El archivo Compose

Coloque esto en /srv/nextcloud/compose.yaml, con los secretos en un archivo hermano .env con modo 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:35-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:35-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:

Fije la versión mayor y compruebe la actual en Docker Hub antes de copiar 35 literalmente. A fecha de 21 de octubre de 2026, Nextcloud mantiene las versiones 33, 34 y 35. La versión 31 llegó al fin de su vida útil en febrero de 2026 y la 32 en septiembre de 2026, por lo que una etiqueta copiada de una guía antigua instalará una versión que ya no recibe correcciones. latest le hará saltar entre versiones mayores en algún docker compose pull futuro, y Nextcloud no admite ese procedimiento.

El directorio de datos es un montaje de tipo bind, no un volumen con nombre, por un motivo específico: una ruta a la que pueda apuntar directamente una herramienta de copia de seguridad es más valiosa que el orden. Créelo con el UID www-data de la imagen y los permisos que exige Nextcloud:

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

Observe la publicación del puerto: 127.0.0.1:8080:80. Docker publica puertos escribiendo reglas DNAT que se evalúan antes de que la cadena INPUT de ufw vea el paquete; un 8080:80 simple expone un Nextcloud sin cifrar a la internet pública sin importar lo que diga ufw. Vincularlo al loopback lo mantiene fuera de la interfaz pública. Entonces, el firewall solo tiene que permitir el tráfico del proxy, y si prefiere no dejar SSH abierto a toda la internet, acceder al VPS a través de una VPN WireGuard autohospedada le permite eliminar el puerto 22 de las reglas públicas por completo:

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Inícielo con docker compose up -d, luego observe docker compose logs -f app. El primer arranque copia todo el árbol de la aplicación en el volumen y ejecuta el instalador; el contenedor no responde a nada hasta que ese proceso finaliza.

TLS y el proxy inverso

Instale nginx y certbot desde los repositorios de la distribución, cree un bloque de servidor sencillo para el puerto 80 con el server_name correcto y deje que certbot lo reescriba. El funcionamiento del desafío HTTP-01, el temporizador de renovación y los modos de fallo se explican por completo en emitir certificados de Let's Encrypt con certbot y nginx en Ubuntu 24.04:

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

Certbot añade las líneas ssl_certificate y la redirección :80 → :443. También instala un temporizador de systemd que renueva el certificado de 90 días. Confirme que existe con systemctl list-timers | grep certbot. Un temporizador de renovación que nunca se habilitó es una cuenta atrás de 90 días.

El bloque del proxy es el siguiente:

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;
    }
}

En nginx 1.25.1 y versiones posteriores, añada http2 on;. La directiva no existe en 1.25.0 y nginx no se iniciará si la encuentra. Ubuntu 24.04 incluye una compilación anterior en la que el equivalente es listen 443 ssl http2;. nginx -t le indicará cuál de las dos acepta su compilación.

Si client_max_body_size es demasiado pequeño, nginx rechaza directamente una carga grande con un error 413. Si los tiempos de espera de lectura son demasiado cortos, la carga falla a mitad de camino. Los valores anteriores evitan ambos problemas. proxy_request_buffering off transmite la carga a través del proxy en lugar de almacenar primero el archivo completo en su disco.

nginx en el host es la opción más sencilla para una sola aplicación. Si Nextcloud va a compartir el VPS con otros contenedores, ejecutar Traefik como proxy inverso de Docker Compose para varias aplicaciones traslada el enrutamiento y la emisión de certificados a las etiquetas de los contenedores. Allí vuelven a aparecer las mismas consideraciones sobre client_max_body_size y los tiempos de espera, como ajustes de middleware y transporte.

trusted_proxies y overwriteprotocol

Aquí es donde la mayoría de las instancias de Nextcloud autohospedadas fallan, y los síntomas parecen no tener relación con la causa.

X-Forwarded-Proto: https solo se respeta cuando la petición llega desde una dirección listada en trusted_proxies. Cuando no se respeta, Nextcloud cree que la petición es HTTP plano y emite URLs http://; el proxy redirige esas a HTTPS; el navegador sigue la redirección; Nextcloud emite http:// de nuevo. Ese es el bucle de redirección. OVERWRITEPROTOCOL: https fija el esquema independientemente.

La trampa en TRUSTED_PROXIES es que la dirección que Nextcloud ve no es 127.0.0.1. nginx se ejecuta en el host y se conecta a un puerto publicado, por lo que el contenedor ve la puerta de enlace del puente de Docker, algo en 172.x. Encuentre la subred real:

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

Ponga ese CIDR (o el 172.16.0.0/12 que lo cubre) en TRUSTED_PROXIES. Si lo configura demasiado amplio, cualquier cliente podría suplantar X-Forwarded-For; si lo configura mal, cada inicio de sesión parecerá provenir de la dirección de la puerta de enlace, la protección contra fuerza bruta bloqueará toda su instancia a la vez y la vista general de administración mostrará "La configuración de cabecera del proxy inverso es incorrecta, o está accediendo a Nextcloud desde un proxy de confianza."

OVERWRITECLIURL es importante para el contenedor cron, el cual no tiene una petición entrante de la cual inferir un nombre de host. Sin esto, los trabajos en segundo plano generan enlaces a localhost y las notificaciones por correo electrónico envían URLs inutilizables.

Trabajos en segundo plano: cron, no AJAX

El ejecutor de trabajos predeterminado de Nextcloud es AJAX: los trabajos se ejecutan como efecto secundario de que alguien cargue una página. Nadie navega a las 04:00, por lo que la caducidad de la papelera, la limpieza de versiones, las vistas previas y los reintentos federados se detienen. El primer síntoma es que el directorio de datos no deja de crecer. El servicio cron anterior ejecuta el bucle oficial de /cron.sh sobre los mismos volúmenes. Indique a Nextcloud que debe esperarlo:

docker compose exec -u www-data app php occ background:cron

Todos los comandos de occ siguen esta estructura: docker compose exec -u www-data app php occ <command>. Conviene crear un alias.

Copias de seguridad: tres elementos o ninguno

Una copia de seguridad que sólo incluye el sistema de archivos restaura una instancia dañada. El directorio de datos contiene los bytes; Postgres contiene la caché de archivos, los recursos compartidos, los usuarios y el estado de la aplicación; config.php contiene las credenciales de la base de datos, el ID de la instancia y la sal de la contraseña. Si restaura los archivos sin la base de datos, Nextcloud no puede verlos. Si restaura la base de datos sin config.php, no puede abrir la base de datos. Si restaura una base de datos antigua sobre un directorio de datos más reciente, obtendrá recursos compartidos que apuntan a archivos que han cambiado de ubicación.

Haga una copia de seguridad de los tres elementos con la instancia detenida:

#!/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/

El modo de mantenimiento hace que el volcado y la copia de archivos sean coherentes entre sí. Si lo omite, tarde o temprano capturará una base de datos que hace referencia a un archivo al que rsync todavía no había llegado. Tenga en cuenta que el script conserva los volcados de la base de datos con marcas de tiempo, pero sólo un espejo rotatorio del directorio de datos; rsync --delete lo sobrescribe en cada ejecución. Por tanto, sólo el volcado más reciente se corresponde con la copia de archivos.

Después, saque la copia del servidor. Una copia de seguridad que reside en el mismo VPS que el sistema que protege es una copia, no una copia de seguridad. restic con almacenamiento de objetos o con un segundo host es la opción habitual, y su deduplicación gestiona el directorio de datos mucho mejor que un archivo tar nocturno. La configuración completa, desde la inicialización del repositorio hasta el temporizador nocturno y la prueba de restauración, está en copias de seguridad de VPS fuera del servidor con restic.

La restauración no consiste simplemente en invertir el proceso. Una pila iniciada desde cero ejecuta el instalador y escribe un config.php completamente nuevo, un ID de instancia y una sal de contraseña nuevos. Importar el volcado sobre esa identidad nueva deja sesiones y tokens de recursos compartidos dañados. Restaure primero la identidad anterior, en este orden:

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 reconcilia la caché de archivos con lo que realmente existe en el disco. Practique este procedimiento una vez en un VPS de reserva, antes de necesitarlo. La misma separación entre los bytes del disco y los metadatos de Postgres se aplica a cualquier otra aplicación de este tipo. Por eso una copia de seguridad de Immich que captura la biblioteca pero no la base de datos restaura una línea temporal vacía.

Actualizaciones: una versión mayor a la vez

Nextcloud permite actualizar exactamente una versión mayor a la vez. Con la imagen oficial de Docker, saltar de la 29 a la 31 nunca llega al actualizador. El punto de entrada compara las versiones mayores antes de ejecutar occ upgrade, imprime Can't start Nextcloud because upgrading from <installed version> to <image version> is not supported. seguido de It is only possible to upgrade one major version at a time. y finaliza. El contenedor se niega a iniciar (con restart: unless-stopped se reinicia constantemente imprimiendo las mismas líneas), el modo de mantenimiento nunca se activa y los datos y la base de datos permanecen intactos. Cambie la etiqueta a la siguiente versión mayor e inicie de nuevo.

La actualización en Docker consiste en: realizar una copia de seguridad, editar la etiqueta a una versión mayor superior tanto en los servicios app como cron (de 35 a 36 una vez que la 36 se publique, lo cual Nextcloud planea para febrero de 2027), luego docker compose pull && docker compose up -d y después docker compose logs -f app. El punto de entrada de la imagen detecta el código más reciente frente a los datos existentes y ejecuta occ upgrade automáticamente. No lo interrumpa. Cuando los registros dejen de mostrar actividad, ejecute docker compose exec -u www-data app php occ status y verifique versionstring, además de comprobar que las aplicaciones se hayan reactivado.

Dos reglas que le salvarán: incremente una versión mayor, verifique y luego incremente la siguiente. Nunca edite la etiqueta en el servicio app sin editar cron para que coincidan; tener dos versiones distintas de Nextcloud contra una misma base de datos es una vía directa a la corrupción.

Los errores que verá realmente

"Your data directory is readable by other people. Please change the permissions to 0770 so that the directory cannot be listed by other people." El último dígito del modo del directorio, el correspondiente a otros usuarios, no es cero. Cualquier bit de lectura, escritura o ejecución para otros usuarios provoca este error. Los bits del grupo nunca se comprueban, porque 0770 ya concede acceso completo al grupo. Nextcloud primero intenta ejecutar chmod 0770 por su cuenta y sólo muestra una advertencia si falla. En un bind mount, esto suele indicar que el directorio no pertenece al UID 33. sudo chown -R 33:33 /srv/nextcloud/data y sudo chmod 0770 /srv/nextcloud/data.

"Your data directory is invalid. Ensure there is a file called ".ncdata" in the root of the data directory." El bind mount apunta a una ubicación que Nextcloud nunca inicializó, hay un error tipográfico en la ruta o se ha sustituido un directorio vacío por el directorio de una instancia funcional. Compruebe que la ruta del host coincide con la línea del volumen.

"Access through untrusted domain." El nombre de host de la petición no figura en trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS sólo se aplica durante la instalación inicial. Después, configúrelo en ejecución con occ config:system:set trusted_domains 1 --value=cloud.example.com.

502 Bad Gateway, con connect() failed (111: Connection refused) while connecting to upstream en /var/log/nginx/error.log. nginx no encontró ningún servicio escuchando en 127.0.0.1:8080. El contenedor todavía se está inicializando; compruébelo con docker compose logs app. También puede haberse detenido; compruébelo con docker compose ps. Otra posibilidad es que la línea de publicación no coincida con el puerto proxy_pass. Confírmelo con ss -ltnp | grep 8080.

Un bucle de redirección o advertencias de conexión "insecure" en la vista general de administración. Falta OVERWRITEPROTOCOL: https o TRUSTED_PROXIES no contiene la subred de la puerta de enlace de Docker. Consulte la sección anterior sobre el proxy.

LockedException: "files/..." is locked. Con REDIS_HOST configurado, la imagen establece Redis como backend de bloqueo y los bloqueos obsoletos son poco frecuentes. Sin esta configuración, los bloqueos se almacenan en la tabla de base de datos oc_file_locks y una petición terminada durante una escritura deja filas residuales. Confirme que Redis se está utilizando realmente. occ config:system:get memcache.locking debe devolver la clase de Redis antes de eliminar manualmente las filas de bloqueos.

"The PHP memory limit is below the recommended value of 512 MB." Aumente PHP_MEMORY_LIMIT y vuelva a crear el contenedor. Recuerde cómo afecta esto al límite máximo en el peor caso.

Qué falla al escalar

El primer límite aparece cuando el directorio de datos supera el tamaño del volumen. Ampliar un volumen en un VPS requiere redimensionarlo y ampliar el sistema de archivos. Es mucho menos problemático programar esta tarea antes de alcanzar el 100 %. Configure ahora las alertas de uso de disco, no después.

El segundo límite es oc_filecache. Los listados de archivos y los análisis de sincronización se ralentizan a medida que aumenta el número de filas. La solución requiere trabajo en la base de datos: mantenga Postgres en almacenamiento rápido, permítale usar suficiente memoria compartida y elimine la papelera y las versiones mediante ajustes de retención, en lugar de dejar que se acumulen indefinidamente.

El tercer problema es que la generación de vistas previas compite con el resto de las tareas. En un servidor pequeño, limite los proveedores de vistas previas y no ejecute nunca occ preview:generate-all durante el horario de trabajo. Si la mayor parte de los datos almacenados procede del carrete de la cámara de un teléfono, esa generación de miniaturas debería ejecutarse en un servidor de fotos específico. Comparativa de PhotoPrism e Immich en memoria RAM, aplicaciones para teléfonos y comandos de copia de seguridad explica el coste de cada opción junto a un servidor Nextcloud.

A partir de ahí, la respuesta práctica es que los servicios adicionales necesitan su propio equipo. Collabora y la búsqueda de texto completo son servicios residentes independientes, con sus propios perfiles de memoria. Instalarlos en el mismo equipo que contiene la única copia de los archivos amplía el dominio de fallo sin aportar ninguna ventaja. Si quiere añadir la edición de documentos desde el navegador, los requisitos mínimos de RAM del proveedor y los límites de conexiones que diferencian OnlyOffice de Collabora determinan cuál de los dos puede funcionar en un VPS de 2 a 4 GB. Traslade el almacenamiento de archivos a un almacenamiento principal compatible con S3 cuando el volumen deje de tener el tamaño adecuado. Tenga en cuenta que esto dificulta las copias de seguridad, no que las simplifica: la base de datos sigue conteniendo los metadatos y debe volcarse al mismo tiempo que el bucket.

Cuando la instancia ya atienda a usuarios reales, coloque Uptime Kuma delante para recibir avisos de las interrupciones antes que los clientes de sincronización. Una nube privada combina bien con su propio servidor de correo. Si prefiere no conectar los servicios manualmente, Cloudron, CasaOS y Coolify comparan las plataformas que lo hacen por usted. Si un motor de búsqueda autoalojado es el siguiente servicio de la lista, espere un problema de otra categoría: los errores 429 de SearXNG pueden deberse a su propio limitador de tasa o a que los motores ascendentes bloquean la IP del VPS. Sólo el registro permite determinar cuál de las dos causas se aplica.

FAQ

¿Puedo ejecutar Nextcloud sobre SQLite en lugar de Postgres?

Puede hacerlo, y la imagen oficial lo permite, pero un solo cliente de sincronización de escritorio enviando peticiones en paralelo alcanzará SQLSTATE[HY000]: General error: 5 database is locked y errores HTTP 500. SQLite aplica un bloqueo de escritura a toda la base de datos, y Nextcloud escribe constantemente: bloqueos de archivos, filas de actividad y estado de tareas. Empiece con Postgres o MariaDB; occ db:convert-type existe, pero es una migración larga y de todo o nada sobre datos en producción.

¿Cuánta RAM necesita realmente un VPS para Nextcloud?

Calcule según la concurrencia, no según el número de usuarios. La memoria residente en el peor de los casos es aproximadamente el número de peticiones concurrentes multiplicado por PHP_MEMORY_LIMIT, más los buffers compartidos de Postgres y un backend por conexión, además de los picos de generación de vistas previas. Un servidor de 2 GB puede ejecutar una instancia doméstica pequeña si limita las vistas previas y añade swap; si añade Collabora o búsqueda de texto completo, estará dimensionando un segundo conjunto de servicios residentes.

¿Por qué fallan las subidas grandes detrás del reverse proxy nginx?

Dos ajustes en el proxy suelen explicarlo. Si client_max_body_size se deja en el valor predeterminado de 1 MB, nginx rechaza cualquier petición mayor directamente con un error 413 (Request Entity Too Large), que aparece en el registro de errores de nginx en lugar de como un archivo parcial. Valores cortos de proxy_read_timeout / proxy_send_timeout interrumpen las transferencias largas a mitad de camino. Establezca ambos valores generosamente, cambie proxy_request_buffering off a stream en lugar de spool, y aumente PHP_UPLOAD_LIMIT en el contenedor de la aplicación para que coincida.

¿Por qué Nextcloud entra en un bucle de redirección o advierte sobre el reverse proxy?

El contenedor no ve a nginx en 127.0.0.1, ve la puerta de enlace del bridge de Docker, en algún lugar de 172.x. Cuando esa dirección falta en TRUSTED_PROXIES, la cabecera X-Forwarded-Proto: https se ignora, Nextcloud emite URLs http:// y el proxy las rebota. Establezca TRUSTED_PROXIES a la subred real del bridge y fije OVERWRITEPROTOCOL: https.

¿Puedo actualizar Nextcloud directamente de la versión 29 a la 31?

No. Nextcloud admite una versión mayor por actualización. En la imagen oficial de Docker, el entrypoint comprueba las versiones mayores antes de ejecutar occ upgrade, imprime It is only possible to upgrade one major version at a time. y se cierra, por lo que el contenedor no arranca y sus datos y base de datos permanecen como estaban. Establezca la etiqueta en 30 en su lugar: haga una copia de seguridad, incremente la etiqueta una versión mayor tanto en los servicios app como cron, docker compose pull && docker compose up -d, verifique con occ status y repita el proceso.