Nextcloud na VPS com Docker, TLS e backups
Configure o Nextcloud em uma VPS com Docker Compose, Postgres, Redis e proxy TLS. Veja como fazer backup restaurável e atualizar sem perder dados.
O que está realmente a criar
Este guia executa o Nextcloud numa VPS com Docker Compose, coloca o TLS do Let's Encrypt à frente do serviço e configura um backup que pode ser efetivamente restaurado. São quatro contentores e um proxy: a imagem oficial nextcloud a escutar na interface de loopback, o Postgres a armazenar todos os metadados dos ficheiros, o Redis a armazenar os bloqueios dos ficheiros, uma segunda cópia da imagem do Nextcloud a executar apenas o ciclo do cron e o nginx no host a terminar o TLS à frente de tudo. A instalação demora vinte minutos, mas não é essa a parte importante. Duas decisões tomadas na primeira hora determinam se ainda terá os seus ficheiros daqui a um ano: usar uma base de dados real em vez do SQLite e ter um backup que capture o diretório de dados, a base de dados e config.php como um conjunto consistente.
Isto pressupõe Ubuntu 24.04 LTS ou Debian 13, Docker Engine com o plugin Compose v2 instalado a partir do repositório oficial do Docker e um registo DNS A (e também AAAA se tiver IPv6) já a apontar cloud.example.com para a VPS. Tudo isto requer um servidor sob o seu controlo. Não é possível terminar o TLS e criar um dump da base de dados num SaaS de terceiros.
Dimensionamento: o que realmente consome a memória
O uso de memória do Nextcloud é dominado por três componentes, e nenhum deles é o próprio "Nextcloud".
Workers PHP. A imagem -apache processa cada pedido simultâneo através de um processo worker que mantém um interpretador PHP. Cada worker pode crescer até PHP_MEMORY_LIMIT antes de o PHP terminar o pedido. A memória residente no pior caso é aproximadamente pedidos simultâneos × limite de memória, e um cliente de sincronização para desktop abre várias ligações paralelas por utilizador. É a simultaneidade, e não o número de utilizadores, que define o limite.
A base de dados. O Postgres cria um processo backend por ligação e mantém buffers partilhados residentes. O conjunto de trabalho aumenta com o número de ficheiros, e não com o número de bytes: oc_filecache mantém uma linha por ficheiro e por utilizador. Cem mil ficheiros pequenos representam uma carga maior para a base de dados do que cem ficheiros grandes.
Geração de pré-visualizações. Gerar uma miniatura descodifica a imagem original na memória com a resolução completa. As pré-visualizações de vídeo executam ffmpeg como processo externo. Executar occ preview:generate-all repete esse pico várias vezes, sem intervalo, e é a forma mais comum de levar um VPS pequeno ao OOM killer.
O Redis é comparativamente económico. Qualquer componente que adicionar mais tarde, como o Collabora, a pesquisa de texto integral ou um antivírus, é um serviço residente separado, com a sua própria utilização de memória, e deve ser incluído no plano de dimensionamento antes de ser ativado.
Se a memória RAM for limitada, ajuste estes parâmetros: reduza PHP_MEMORY_LIMIT, limite preview_max_x / preview_max_y / preview_max_filesize_image, reduza enabledPreviewProviders aos formatos que realmente consulta e defina trashbin_retention_obligation e versions_retention_obligation para que o diretório de dados não cresça silenciosamente até várias vezes o tamanho dos seus ficheiros. Adicione um ficheiro de swap. O swap é lento, mas terminar um processo pelo OOM killer durante uma atualização é pior.
Por que o SQLite falha
O Nextcloud inclui suporte ao SQLite, e a imagem oficial pode utilizá-lo sem problemas aparentes. Não o utilize. O SQLite serializa as escritas com um bloqueio ao nível de toda a base de dados: apenas um processo pode escrever de cada vez em todo o ficheiro. O Nextcloud escreve constantemente: bloqueios de ficheiros, registos de atividade, entradas de cache e estado das tarefas. Além disso, um único cliente de ambiente de trabalho a sincronizar uma árvore de diretórios gera muitos pedidos paralelos. Nesse cenário, ocorre SQLSTATE[HY000]: General error: 5 database is locked e surgem erros HTTP 500. A falha aparece precisamente quando a instância começa a ser útil.
Mais tarde, é possível converter a base de dados com occ db:convert-type, mas trata-se de uma migração longa e indivisível num conjunto de dados ativo. Comece com Postgres ou MariaDB.
O ficheiro Compose
Coloque isto em /srv/nextcloud/compose.yaml, com os segredos num ficheiro .env adjacente, com o 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: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:Fixe a tag principal e confirme a tag atual no Docker Hub antes de copiar 31 literalmente. latest fará a atualização através de um limite de versão principal num futuro docker compose pull, e o Nextcloud não suporta isso.
O diretório de dados é uma montagem bind, e não um volume nomeado, de propósito: um caminho que pode indicar diretamente a uma ferramenta de backup é mais importante do que a organização. Crie-o com o UID www-data da imagem e as permissões exigidas pelo Nextcloud:
sudo mkdir -p /srv/nextcloud/data
sudo chown -R 33:33 /srv/nextcloud/data
sudo chmod 0770 /srv/nextcloud/dataObserve a publicação da porta: 127.0.0.1:8080:80. O Docker publica portas escrevendo regras DNAT que são avaliadas antes de o pacote chegar à cadeia INPUT do ufw; um 8080:80 simples coloca um Nextcloud sem encriptação na Internet pública, independentemente do que o ufw indicar. A ligação ao loopback mantém o serviço fora da interface pública. Assim, a firewall só precisa de permitir o proxy. Se preferir não deixar o SSH aberto a toda a Internet, aceder ao VPS através de uma VPN WireGuard autoalojada permite remover a porta 22 das regras públicas:
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableInicie-o com docker compose up -d e, em seguida, monitorize docker compose logs -f app. No primeiro arranque, o contentor copia toda a árvore da aplicação para o volume e executa o instalador; o contentor não responde até esse processo terminar.
TLS e o reverse proxy
Instale o nginx e o certbot a partir dos repositórios da distribuição, crie um bloco de servidor simples na porta 80 com o server_name correto e, em seguida, deixe o certbot reescrevê-lo. O funcionamento do desafio HTTP-01, o temporizador de renovação e os modos de falha são explicados em detalhe em emitir certificados Let's Encrypt com certbot e nginx no Ubuntu 24.04:
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d cloud.example.comO certbot adiciona as linhas ssl_certificate e o redirecionamento :80 → :443, além de instalar um temporizador systemd que renova o certificado de 90 dias. Confirme que ele existe com systemctl list-timers | grep certbot. Um temporizador de renovação que nunca foi ativado é uma contagem regressiva de 90 dias até à falha.
O bloco do proxy propriamente dito:
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;
}
}No nginx 1.25 e posteriores, adicione http2 on;. O Ubuntu 24.04 fornece uma versão mais antiga, na qual o equivalente é listen 443 ssl http2;. nginx -t indica qual dos dois é aceite pela sua compilação.
client_max_body_size e os timeouts longos de leitura impedem que uploads grandes falhem a meio. proxy_request_buffering off transmite o upload diretamente, sem guardar primeiro o ficheiro completo no disco do proxy.
O nginx no host é a solução mais simples para uma única aplicação. Se o Nextcloud for partilhar o VPS com outros contentores, executar o Traefik como reverse proxy Docker Compose para várias aplicações transfere o encaminhamento e a emissão de certificados para labels de contentores. As mesmas preocupações com client_max_body_size e timeouts voltam a surgir nas definições de middleware e transporte.
trusted_proxies e overwriteprotocol
É aqui que a maioria das instâncias Nextcloud autoalojadas fica mal configurada. Os sintomas parecem não ter relação com a causa.
X-Forwarded-Proto: https só é aplicado quando o pedido chega de um endereço listado em trusted_proxies. Quando não é aplicado, o Nextcloud considera que o pedido usa HTTP simples e gera URLs http://. O proxy redireciona essas URLs para HTTPS. O navegador segue o redirecionamento. O Nextcloud gera novamente http://. Esse é o ciclo de redirecionamento. OVERWRITEPROTOCOL: https fixa o esquema, independentemente disso.
A armadilha em TRUSTED_PROXIES é que o endereço que o Nextcloud vê não é 127.0.0.1. O nginx é executado no host e liga-se a uma porta publicada. Por isso, o contentor vê o gateway da bridge Docker, que pertence a algo em 172.x. Descubra a sub-rede real:
docker network inspect nextcloud_default \
-f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'Adicione esse CIDR, ou o 172.16.0.0/12 que o abrange, a TRUSTED_PROXIES. Se o intervalo for demasiado amplo, qualquer cliente poderá falsificar X-Forwarded-For. Se estiver incorreto, todos os logins parecerão vir do endereço do gateway, a proteção contra brute force bloqueará toda a instância de uma só vez e a visão geral de administração mostrará "The reverse proxy header configuration is incorrect, or you are accessing Nextcloud from a trusted proxy."
OVERWRITECLIURL é importante para o contentor do cron, que não recebe nenhum pedido de entrada do qual possa inferir um hostname. Sem essa configuração, as tarefas em segundo plano geram links para localhost e as notificações por email enviam URLs inutilizáveis.
Tarefas em segundo plano: cron, não AJAX
O executor de tarefas predefinido do Nextcloud é AJAX: as tarefas são executadas como efeito secundário do carregamento de uma página por alguém. Ninguém navega às 04:00, por isso a expiração do lixo, a limpeza de versões, as pré-visualizações e as novas tentativas federadas ficam bloqueadas. O primeiro sintoma é um diretório de dados que nunca para de crescer. O serviço cron acima executa o ciclo oficial /cron.sh nos mesmos volumes. Configure o Nextcloud para esperar esse executor:
docker compose exec -u www-data app php occ background:cronTodos os comandos occ seguem este formato: docker compose exec -u www-data app php occ <command>. Vale a pena criar um alias.
Backups: three things, or none
Um backup apenas do sistema de ficheiros restaura uma instância avariada. O diretório de dados contém os bytes; o Postgres contém a cache de ficheiros, as partilhas, os utilizadores e o estado da aplicação; config.php contém as credenciais da base de dados, o ID da instância e o salt da palavra-passe. Restaure os ficheiros sem a base de dados e o Nextcloud não consegue vê-los. Restaure a base de dados sem config.php e não consegue abrir a base de dados. Restaure uma base de dados antiga sobre um diretório de dados mais recente e obterá partilhas que apontam para ficheiros que foram movidos.
Faça cópias de segurança dos três componentes, a partir de uma instância colocada em estado quiescente:
#!/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/O modo de manutenção faz com que o dump e a cópia dos ficheiros sejam consistentes entre si. Se o ignorar, acabará por capturar uma base de dados que referencia um ficheiro que o rsync ainda não tinha alcançado. Tenha em atenção que o script mantém dumps da base de dados com timestamp, mas apenas um espelho rotativo do diretório de dados. rsync --delete substitui-o em cada execução, pelo que apenas o dump mais recente corresponde à cópia dos ficheiros.
Depois, retire a cópia do servidor. Uma cópia de segurança que permanece no mesmo VPS que os dados protegidos é uma cópia, não uma cópia de segurança. restic para armazenamento de objetos ou para um segundo host é a resposta habitual, e a deduplicação lida muito melhor com o diretório de dados do que um tarball noturno. A configuração completa, desde a inicialização do repositório até ao timer noturno e ao teste de restauração, está em cópias de segurança de VPS fora do servidor com restic.
A restauração não é simplesmente o processo inverso. Uma stack iniciada de novo executa o instalador e grava um config.php completamente novo, um novo ID de instância e um novo salt da palavra-passe. Importar o dump sobre essa nova identidade deixa as sessões e os tokens de partilha avariados. Reponha primeiro a identidade antiga, pela seguinte ordem:
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 reconcilia a cache de ficheiros com o que existe efetivamente no disco. Faça este procedimento uma vez, num VPS de reserva, antes de precisar dele. A mesma separação entre os bytes no disco e os metadados no Postgres aplica-se a qualquer outra aplicação deste tipo. É por isso que um backup do Immich que captura a biblioteca, mas não a base de dados, restaura uma linha do tempo vazia.
Atualizações: uma versão principal de cada vez
O Nextcloud suporta a atualização de exatamente uma versão principal de cada vez. Saltar da 29 para a 31 não falha de forma controlada. A atualização falha com Exception: Updates between multiple major versions and downgrades are unsupported. e deixa o sistema em modo de manutenção.
O procedimento de atualização do Docker é: fazer uma cópia de segurança, alterar a tag de 31 para 32 nos serviços app e cron, executar docker compose pull && docker compose up -d e depois docker compose logs -f app. O entrypoint da imagem deteta o código mais recente em relação aos dados existentes e executa occ upgrade automaticamente. Não o interrompa. Quando os logs deixarem de apresentar atividade, execute docker compose exec -u www-data app php occ status e confirme versionstring e que as aplicações voltaram a estar ativadas.
Duas regras evitam problemas: atualizar uma versão principal, verificar e só depois atualizar para a seguinte. Nunca altere a tag do serviço app sem alterar cron para a mesma versão. Duas versões diferentes do Nextcloud ligadas à mesma base de dados podem causar corrupção.
Os erros que verá na prática
"Your data directory is readable by other users. Please change the permissions to 0770." O diretório montado por bind tem bits de leitura para o grupo ou para outros utilizadores. sudo chmod 0770 /srv/nextcloud/data e sudo chown -R 33:33 /srv/nextcloud/data.
"Your data directory is invalid. Ensure there is a file called .ocdata in the root." O bind mount aponta para um local que o Nextcloud nunca inicializou, para um caminho com um erro de escrita ou para um diretório vazio criado recentemente e colocado no lugar de uma instância funcional. Confirme que o caminho no host corresponde à linha do volume.
"Access through untrusted domain." O hostname no pedido não está em trusted_domains. NEXTCLOUD_TRUSTED_DOMAINS só se aplica durante a instalação inicial; depois, altere a configuração em execução: occ config:system:set trusted_domains 1 --value=cloud.example.com.
502 Bad Gateway, com connect() failed (111: Connection refused) while connecting to upstream em /var/log/nginx/error.log. O nginx não encontrou nada em 127.0.0.1:8080. O contentor ainda pode estar a ser inicializado (verifique docker compose logs app), pode ter terminado (docker compose ps) ou a linha de publicação pode não corresponder à porta proxy_pass. Confirme com ss -ltnp | grep 8080.
Um ciclo de redirecionamento ou avisos de ligação "insecure" na vista geral de administração. Falta OVERWRITEPROTOCOL: https ou TRUSTED_PROXIES não inclui a sub-rede do gateway Docker. Consulte a secção anterior sobre o proxy.
LockedException: "files/..." is locked. Com REDIS_HOST definido, a imagem configura o Redis como backend de bloqueios e os bloqueios obsoletos são raros. Sem essa configuração, os bloqueios ficam na tabela da base de dados oc_file_locks e um pedido terminado durante uma escrita deixa linhas para trás. Confirme que o Redis está realmente a ser utilizado: occ config:system:get memcache.locking deve devolver a classe Redis, antes de apagar manualmente as linhas de bloqueio.
"The PHP memory limit is below the recommended value of 512MB." Aumente PHP_MEMORY_LIMIT e recrie o contentor. Tenha em conta o efeito disso no limite máximo do pior caso.
O que falha em escala
O primeiro limite surge quando o diretório de dados ultrapassa o tamanho do volume. Aumentar um volume numa VPS exige redimensioná-lo e aumentar o sistema de ficheiros. É muito menos doloroso fazer isso de forma agendada do que esperar pelos 100% de utilização. Configure já alertas para a utilização do disco.
O segundo limite é oc_filecache. As listagens de ficheiros e as verificações de sincronização ficam mais lentas à medida que aumenta o número de registos. A correção exige trabalho na base de dados: mantenha o Postgres em armazenamento rápido, permita que use memória partilhada suficiente e elimine o lixo e as versões com definições de retenção, em vez de os deixar acumular indefinidamente.
O terceiro limite é a geração de pré-visualizações, que compete com todos os outros processos. Numa máquina pequena, mantenha os fornecedores de pré-visualizações limitados e nunca execute occ preview:generate-all durante o horário de trabalho. Se a maior parte do que armazena for o rolo da câmara de um telemóvel, esse trabalho de criação de miniaturas deve ser feito num servidor de fotografias dedicado. O artigo Comparação entre PhotoPrism e Immich quanto à RAM, aplicações móveis e comandos de backup explica os recursos de cada opção quando usada juntamente com uma máquina Nextcloud.
A partir daí, a resposta honesta é que os componentes adicionais devem ter a sua própria máquina. O Collabora e a pesquisa de texto integral são serviços residentes separados, com os seus próprios perfis de consumo de memória. Colocá-los na máquina que também contém a sua única cópia dos ficheiros aumenta o domínio de falha sem trazer benefícios. Se pretende edição de documentos no navegador, os limites mínimos de RAM e de ligações definidos pelos fornecedores, descritos em os requisitos de RAM e os limites de ligações que distinguem o OnlyOffice do Collabora, determinam qual deles uma VPS de 2 a 4 GB consegue executar. Transfira o armazenamento dos ficheiros para armazenamento primário compatível com S3 quando o volume deixar de ter o formato adequado. Tenha em conta que isto torna os backups mais difíceis, não mais fáceis: a base de dados continua a conter os metadados e tem de ser exportada em conjunto com o bucket.
Quando a instância começar a servir utilizadores reais, coloque o Uptime Kuma à frente dela para ser informado da indisponibilidade antes dos clientes de sincronização. Uma cloud privada combina bem com o seu próprio servidor de correio. Se preferir não ligar os serviços manualmente, Cloudron, CasaOS e Coolify comparam as plataformas que fazem isso por si. Se um motor de pesquisa self-hosted for o próximo componente da lista, espere um tipo de problema diferente dos anteriores: os erros 429 do SearXNG resultam do seu próprio limitador de taxa ou do bloqueio do endereço IP da VPS pelos motores upstream. Só o log permite determinar qual das duas causas ocorreu.
FAQ
Posso executar o Nextcloud com SQLite em vez de Postgres?
Pode, e a imagem oficial permite fazê-lo, mas um único cliente de sincronização de desktop a emitir pedidos em paralelo vai atingir SQLSTATE[HY000]: General error: 5 database is locked e gerar erros HTTP 500. O SQLite bloqueia as escritas ao nível de toda a base de dados, e o Nextcloud escreve constantemente dados como bloqueios de ficheiros, registos de atividade e estado dos trabalhos. Comece com Postgres ou MariaDB; occ db:convert-type existe, mas é uma migração longa e indivisível com os dados em produção.
De quanta RAM precisa realmente um VPS com Nextcloud?
Dimensione para a concorrência, não para o número de utilizadores. A memória residente máxima é aproximadamente o número de pedidos concorrentes multiplicado por PHP_MEMORY_LIMIT, acrescido dos buffers partilhados do Postgres e de um backend por ligação, além do pico causado pela geração de pré-visualizações. Uma máquina com 2 GB executa uma instância doméstica pequena se limitar as pré-visualizações e adicionar swap; ao adicionar Collabora ou pesquisa de texto integral, terá de dimensionar outro conjunto de serviços residentes.
Porque falham os uploads grandes atrás do reverse proxy nginx?
Normalmente, duas definições do proxy explicam o problema: client_max_body_size, com o valor predefinido de 1 MB, trunca o pedido, e valores curtos de proxy_read_timeout / proxy_send_timeout terminam as transferências longas a meio. Defina ambos com valores suficientemente elevados, configure proxy_request_buffering off para fazer streaming em vez de armazenar em spool e aumente PHP_UPLOAD_LIMIT no contentor da aplicação para corresponder.
Porque é que o Nextcloud entra num ciclo de redirecionamentos ou apresenta um aviso sobre o reverse proxy?
O contentor não vê o nginx em 127.0.0.1. Vê o gateway da bridge Docker, algures em 172.x. Quando esse endereço está ausente de TRUSTED_PROXIES, o cabeçalho X-Forwarded-Proto: https é ignorado, o Nextcloud gera URLs http:// e o proxy redireciona-as novamente. Defina TRUSTED_PROXIES para a sub-rede real da bridge e fixe OVERWRITEPROTOCOL: https.
Posso atualizar o Nextcloud diretamente da versão 29 para a 31?
Não. O Nextcloud suporta uma versão principal por atualização. Se saltar uma versão, a atualização termina com Updates between multiple major versions and downgrades are unsupported. e deixa a instância em modo de manutenção. Faça uma cópia de segurança, aumente a tag em uma versão principal nos serviços app e cron, execute docker compose pull && docker compose up -d, confirme com occ status e repita.