Deploy Nextcloud with PostgreSQL, Redis, TLS, Cron, backups, and verified restore
Deploy Nextcloud 34.0.2 on Ubuntu 24.04 with protected PostgreSQL and Redis, canonical Nginx TLS, observable Cron, complete backups, and an isolated application-level restore drill.
Operate a secure, maintainable Nextcloud service whose web, database, locking, jobs, data protection, updates, and recovery are verified end to end.
- Nextcloud Server 34.0.2
- Ubuntu Server 24.04 LTS
- PHP 8.3
- PostgreSQL 16
- Redis 7.x
- Service identity and host Canonical DNS/TLS ownership, Ubuntu 24.04 host, synchronized time, protected administration, capacity/growth plan and current patch ownership exist.
- Data and security governance Named admins, user/app/external-storage scope, upload/retention policy, secret/key custody, monitoring and incident ownership are approved.
- Independent recovery Off-host immutable backup destination, encryption/key recovery, isolated restore DNS/network/storage and named RPO/RTO acceptance owner are ready.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A production-oriented Nextcloud 34.0.2 service on Ubuntu 24.04 LTS using Nginx, PHP 8.3 FPM, a dedicated PostgreSQL 16 database role, APCu local cache, Redis 7 over a protected Unix socket for distributed cache and transactional file locking, systemd-driven Cron, canonical TLS, and user data outside the web root.
- An independently recoverable service with one-generation database/config/data/custom-app/theme backups, integrity manifests, protected database authentication, isolated restore, fresh data fingerprint, application-level acceptance, controlled upgrades, and a twice-yearly full rebuild drill.
- The canonical HTTPS endpoint validates externally, denies sensitive paths, routes DAV correctly, reports installed Nextcloud 34.0.2 without maintenance/database-upgrade flags, and passes setup checks plus a synthetic file lifecycle.
- PostgreSQL uses a dedicated non-superuser role, Redis has no TCP listener and answers only through its protected socket, cache/locking are active, and background jobs execute as `www-data` on an observable five-minute timer.
- A selected full backup generation restores to an isolated FQDN/database/data path with matching hashes, apps, shares and user files, while `maintenance:data-fingerprint`, DAV and application checks prove safe client reconciliation inside the accepted RPO/RTO.
Architecture
How the parts fit together
The canonical TLS endpoint terminates at Nginx and serves replaceable Nextcloud 34 application code from `/var/www/nextcloud`; durable data stays under `/srv/nextcloud-data`. PHP 8.3 FPM runs requests and `occ` administrative jobs as `www-data`. PostgreSQL 16 stores metadata under a dedicated non-superuser role. APCu caches local objects, while Redis 7 exposes only a group-restricted Unix socket for distributed cache and transactional file locking. A systemd timer runs background jobs. An encrypted off-host backup generation captures database, configuration, data, custom apps and themes together; an isolated environment proves restore and client-facing behavior.
- Clients validate the canonical certificate and connect to Nginx; HTTP redirects once to HTTPS while sensitive paths are denied.
- Nginx passes allowed front-controller/PHP requests to PHP-FPM, which reads protected Nextcloud configuration and durable data.
- Nextcloud commits metadata to the dedicated PostgreSQL database and coordinates cache/locks through APCu plus the Redis Unix socket.
- The systemd timer invokes `occ system:cron` as `www-data`; health checks observe status, setup, logs, DAV, TLS, storage and a synthetic file.
- A maintenance-window backup captures database, config, data, custom apps and themes as one hashed off-host generation.
- A separate FQDN/database/data path receives the restore, repair and new data fingerprint; synthetic user/application checks prove recovery before any cutover.
Assumptions
- The service uses Nextcloud 34.0.2, Ubuntu Server 24.04 LTS, PHP 8.3, PostgreSQL 16, Redis 7 and Nginx from maintained trusted repositories, and selected apps support that matrix.
- Canonical forward DNS, an externally valid certificate, synchronized time, protected administrative SSH, capacity alerting and host recovery access are controlled.
- This profile is one application host with local database/cache. A cluster, remote database, object storage, proxy tier or identity provider requires its own availability, TLS, consistency and backup review.
- Durable data lives on a supported local filesystem outside the web root with enough inode/capacity/performance headroom; external storage is backed up by its owner with the matching database generation.
- A secret store protects administrator/database credentials, `.pgpass`, server-side encryption/recovery material, TLS private keys and backup encryption keys; none appear in commands or evidence.
- The organization owns user-data governance, retention, legal holds, malware/ransomware response, app approval and acceptance of restored accounts, files, shares and notifications.
Key concepts
- Trusted domain
- Explicit hostname Nextcloud accepts in requests; it prevents arbitrary Host values from becoming application identity.
- Trusted proxy
- Exact reverse-proxy address allowed to supply client/protocol headers; broad trust lets clients spoof security-relevant metadata.
- Transactional file locking
- Nextcloud coordination that prevents conflicting file operations; Redis is recommended to reduce database load and improve correctness.
- APCu local cache
- Fast cache private to a PHP process/host; it complements rather than replaces distributed Redis cache.
- Maintenance mode
- Application state that blocks normal user access while a coordinated backup, update or restore establishes consistency.
- Data fingerprint
- Marker changed after restoring server-side files/database so synchronization clients can recognize that remote state moved backward or changed externally.
- Complete generation
- Database, config, data, custom app/theme code and required keys captured at one logical point and retained together.
Before you copy
Values used in this guide
{{nextcloudFqdn}}Canonical public HTTPS hostname.
Example: cloud.example.net{{nextcloudRoot}}Replaceable application code root.
Example: /var/www/nextcloud{{nextcloudDataParent}}Filesystem parent validated for capacity and features.
Example: /srv{{nextcloudData}}Durable user data outside the Nginx document root.
Example: /srv/nextcloud-data{{nextcloudArchive}}Path to exact official 34.0.2 archive.
Example: /srv/releases/nextcloud-34.0.2.tar.bz2{{dbRole}}Dedicated non-superuser PostgreSQL role.
Example: nextcloud{{dbName}}Dedicated PostgreSQL database.
Example: nextcloud{{uploadLimit}}Reviewed Nginx/PHP/proxy upload ceiling.
Example: 10G{{backupUser}}Restricted local database-backup identity using peer or protected pgpass authentication.
Example: nextcloud-backup{{backupSet}}secretNew exact off-host backup generation mount/path.
Example: /mnt/backup/nextcloud/2026-07-28T010000Z{{restoreFqdn}}Isolated restore-only hostname.
Example: cloud-restore-20260728.lab.example.net{{restoreRoot}}Empty application root on isolated restore host.
Example: /srv/restore/nextcloud{{restoreData}}Empty isolated data path.
Example: /srv/restore/nextcloud-data{{restoreDbUser}}OS identity permitted to run protected restore.
Example: nextcloud-restore{{restoreDbRole}}Dedicated isolated PostgreSQL application role.
Example: nextcloud_restore{{restoreDbName}}Empty isolated PostgreSQL database.
Example: nextcloud_restore{{testUser}}Synthetic least-privilege acceptance identity.
Example: restore-probeSecurity and production boundaries
- Keep hypervisor and application management interfaces off untrusted networks. Restrict by firewall and administrative source, require TLS hostname validation and MFA where supported, and never expose management simply because the web interface has authentication.
- Separate service availability from recoverability. RAID, ZFS mirrors, replication, snapshots, VM checkpoints, database replicas, and application maintenance mode remain inside related failure domains and do not replace an independent tested backup.
- Before destructive storage, database, network, certificate, or restore operations, require two-person target verification, current backups, independent console access, capacity headroom, application quiescing, and explicit stop/rollback criteria.
- Capture examples using synthetic identifiers and data. Redact host serials, MACs where sensitive, internal addresses, usernames, paths revealing tenants, tokens, passwords, encryption keys, recovery keys, and file content.
- Do not expose PostgreSQL, Redis, PHP-FPM, the data directory, config, private keys or backup endpoints to users. Nginx and a protected administrative path are the only intended ingress in this profile.
- Nextcloud stores sensitive user data and authentication/share metadata. Administrators must not browse real files for routine testing; use synthetic accounts and redact logs, hostnames, user IDs, share tokens, file paths and content.
- Server-side encryption does not replace TLS, host/storage security or backup key custody. Losing application/recovery keys can make a technically successful restore unreadable.
Stop before continuing if
- Stop if any secret is requested in command arguments, screenshots, tickets or validation output; switch to interactive or secret-manager delivery.
- Stop if official release checksum, supported version matrix, repository origin, TLS hostname/chain, trusted domain/proxy, data-path ownership or sensitive-path denial cannot be proven.
- Stop user onboarding while PostgreSQL role isolation, Redis locking, Cron, capacity alerts, complete off-host backup/key recovery or isolated restore remain unverified.
- Stop backup/update/restore when the exact target, consistency generation, maintenance state, database/file/app versions or rollback owner is ambiguous.
- Never connect an isolated restore to production DNS, clients, federation, mail or identity until a separately approved cutover plan passes all acceptance gates.
instruction
Freeze the service, security, and recovery design
Define the canonical HTTPS hostname, DNS ownership, user/concurrency/storage growth, upload ceiling, RPO/RTO, maintenance window, retention, legal ownership, external-storage scope, notification path, administrator identities, and restore acceptance before installing. Document whether Nginx terminates TLS directly or an exact trusted proxy does; this guide assumes a single Ubuntu host with local PostgreSQL, Redis, PHP-FPM, and data outside the web root.
Why this step matters
Hostname, capacity and recovery choices are architectural inputs. Retrofitting them after users synchronize data creates avoidable outages, certificate errors, duplicate content, and uncertain restore ownership.
What to understand
Reserve data, database, backup staging, log and update working space separately; alerts must fire before filesystem or PostgreSQL exhaustion.
Set measurable RPO/RTO and identify the business owner who will accept restored files, sharing, authentication, apps and clients.
If a reverse proxy exists, record its exact source addresses and header behavior; never trust an entire private range merely for convenience.
System changes
- Creates an approved build and recovery record only; the host and service remain unchanged.
Syntax explained
canonical FQDN- Single supported user-visible identity used for TLS, trusted domains, DAV and recovery tests.
RPO / RTO- Maximum acceptable data loss and restoration time that determine backup frequency and drill design.
data outside web root- Reduces accidental HTTP exposure and separates durable user data from replaceable application code.
Values stay on this page and are never sent or saved.
getent ahosts {{nextcloudFqdn}}; timedatectl; lsblk -f; df -hT {{nextcloudDataParent}}; stat -f -c 'type=%T block=%S' {{nextcloudDataParent}}; openssl s_client -connect {{nextcloudFqdn}}:443 -servername {{nextcloudFqdn}} </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates203.0.113.42 STREAM cloud.example.net Local time: Tue 2026-07-28 09:40:12 CEST System clock synchronized: yes /dev/mapper/vg-nextcloud xfs 1.8T 620G 1.2T 35% /srv/nextcloud-data type=xfs block=4096 subject=CN = cloud.example.net issuer=C = US, O = Let's Encrypt, CN = E7 notAfter=Oct 19 07:22:10 2026 GMT
Checkpoint: Checkpoint: Freeze the service, security, and recovery design
Continue whenDNS, TLS, synchronized time, storage headroom, ownership, trusted-proxy decision, RPO/RTO, backup destination and restore acceptance are written and approved.
Stop whenDNS or certificate ownership is uncertain, storage cannot meet growth/backup headroom, proxy boundary is vague, or no owner accepts recovery.
If this step fails
HTTPS redirects loop or generated links use HTTP behind a reverse proxy.
Likely causeThe proxy is not trusted, forwarded headers are accepted from an untrusted source, or `overwriteprotocol`, `overwritehost`, and the public URL do not describe the actual external path.
curl -IL https://{{nextcloudFqdn}}/loginsudo -u www-data php {{nextcloudRoot}}/occ config:list system --privatesudo nginx -T | grep -nE 'real_ip|X-Forwarded|server_name'
ResolutionTrust only the exact proxy addresses, set overwrite values only when the documented proxy design requires them, sanitize forwarded headers at the trusted proxy, and verify the full external redirect chain.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
verification
Validate Ubuntu, PHP, PostgreSQL, Redis, and host controls
Use Ubuntu Server 24.04 LTS with PHP 8.3, PostgreSQL 16 and Redis 7 from supported repositories for this reviewed profile. Patch the host, confirm repository origin, enable time synchronization, firewall only SSH from management and HTTPS from intended clients, and verify AppArmor plus automatic security update policy before deploying data.
Why this step matters
A known version and repository baseline keeps later incidents diagnosable. Host firewall, time, mandatory access control and updates are part of the application boundary, not unrelated operating-system housekeeping.
What to understand
Review Nextcloud 34 system requirements and the Ubuntu security lifecycle before upgrades; do not assume the newest PHP major is immediately supported.
Do not expose PostgreSQL, Redis or PHP-FPM to a public interface. This profile uses local sockets or loopback-only paths.
Capture installed package origins and pending reboot state so application validation is not invalidated by an unplanned kernel or library transition.
System changes
- Applies approved signed operating-system updates and host controls; may require a scheduled reboot before application work.
Syntax explained
Ubuntu 24.04 LTS- Reviewed host baseline with maintained distribution packages.
PHP 8.3- Supported PHP runtime selected for this guide; extensions and FPM pool must use the same major/minor.
local services- PostgreSQL, Redis and PHP-FPM remain inaccessible from untrusted networks.
lsb_release -ds; uname -r; php -v | head -n 1; psql --version; redis-server --version; apt-cache policy php8.3-fpm postgresql redis-server | sed -n '1,30p'; timedatectl; systemctl is-active apparmor; sudo ufw status verboseUbuntu 24.04.3 LTS 6.8.0-71-generic PHP 8.3.6 (cli) (built: Jun 10 2026 14:21:08) (NTS) psql (PostgreSQL) 16.9 Redis server v=7.0.15 System clock synchronized: yes active Status: active 443/tcp ALLOW IN Anywhere
Checkpoint: Checkpoint: Validate Ubuntu, PHP, PostgreSQL, Redis, and host controls
Continue whenApproved OS/runtime/database/cache versions and repositories, synchronized time, AppArmor, firewall, update policy and reboot state all pass.
Stop whenUnsupported release/runtime, mixed repositories, pending incomplete upgrade, public database/cache listener, disabled time synchronization or absent recovery access.
If this step fails
An upgrade completes partially or an app is incompatible with Nextcloud 34.
Likely causeThe release path skipped a major version, unsupported PHP/PostgreSQL/app versions remain, maintenance prechecks were ignored, or files/database were changed outside the supported updater.
sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u www-data php {{nextcloudRoot}}/occ app:listsudo -u www-data php {{nextcloudRoot}}/occ maintenance:repairsudo -u www-data php {{nextcloudRoot}}/occ log:tail --lines=80
ResolutionKeep maintenance mode enabled, follow the official sequential upgrade path, disable or update incompatible apps, restore the complete pre-upgrade generation if required, and never mix old application files with a migrated database.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
command
Install the reviewed web and application dependencies
Install Nginx, PostgreSQL, Redis, PHP 8.3 FPM/CLI and the modules required by the selected Nextcloud apps. Keep the package list versioned, minimize optional modules, and prove Nginx/PHP/PostgreSQL/Redis services plus extension loading before downloading application code.
Why this step matters
Explicit packages make the runtime reproducible and expose missing PHP capabilities before an interactive install. A short allowlist also reduces the unreviewed extension surface.
What to understand
Add database, image preview or office-integration modules only when the enabled app set requires them and document their resource/security effect.
Use FPM for Nginx; CLI commands must run with the same supported PHP version and effective configuration.
Do not add third-party PPAs merely to obtain a newer PHP build without revalidating Nextcloud support and patch ownership.
System changes
- Installs and enables Nginx, PostgreSQL, Redis, PHP-FPM and reviewed PHP modules.
Syntax explained
php8.3-pgsql- Provides PostgreSQL PDO/database integration used by Nextcloud.
php8.3-apcu- Provides local in-process cache for Nextcloud PHP workers.
php8.3-redis- Provides Redis client support for distributed cache and transactional locking.
sudo apt update && sudo apt install --yes nginx postgresql redis-server php8.3-fpm php8.3-cli php8.3-pgsql php8.3-gd php8.3-curl php8.3-mbstring php8.3-intl php8.3-bcmath php8.3-gmp php8.3-xml php8.3-zip php8.3-imagick php8.3-apcu php8.3-redis unzip bzip2 curl ca-certificates && php -m | grep -E 'apcu|bcmath|curl|gd|gmp|intl|mbstring|pdo_pgsql|redis|xml|zip' && systemctl is-active nginx php8.3-fpm postgresql redis-serverapcu bcmath curl gd gmp intl mbstring pdo_pgsql redis xml zip active active active active
Checkpoint: Checkpoint: Install the reviewed web and application dependencies
Continue whenAll reviewed packages come from trusted repositories, required extensions appear once, and four services report active with no failed unit.
Stop whenAPT signature/origin fails, an unsupported runtime is selected, extension/service fails, or an unexpected public listener appears.
If this step fails
The browser shows 502 Bad Gateway after the Nginx configuration is enabled.
Likely causeNginx points at the wrong PHP-FPM socket, PHP-FPM is stopped, the pool name differs, or Unix-socket permissions do not permit the web worker to connect.
systemctl status nginx php8.3-fpm --no-pagersudo ss -xl | grep phpsudo nginx -T | grep -n fastcgi_passsudo journalctl -u nginx -u php8.3-fpm -n 80 --no-pager
ResolutionMatch `fastcgi_pass` to the active PHP 8.3 pool socket, correct the supported service configuration, reload PHP-FPM and Nginx, and repeat both the local and external HTTPS checks.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
command
Create a least-privilege PostgreSQL database and role
Create a dedicated login role using PostgreSQL's protected password prompt, then create a UTF-8 database owned by only that role. Do not put the password in shell history, command arguments, process listings, screenshots, or tutorial variables. Keep PostgreSQL local and preserve the role/database names in the recovery record.
Why this step matters
A service-specific non-superuser limits database blast radius. Interactive secret entry prevents the credential from becoming an observable process argument, while explicit ownership keeps schema creation supportable.
What to understand
Use a password manager or secrets platform to generate and escrow the database credential; the example output deliberately omits it.
Prefer local socket/host rules narrowly scoped to the Nextcloud role and database. Do not solve authentication failures by adding a blanket `trust` rule.
The application role must not be PostgreSQL superuser, role creator, database creator or replication role.
System changes
- Creates one PostgreSQL login role and one empty UTF-8 database owned by that role.
Syntax explained
--pwprompt- Reads the database secret interactively rather than exposing it in arguments.
--owner- Makes the dedicated application role owner of its database.
--template=template0- Creates a clean database using the requested encoding/locale rather than inheriting site objects.
Values stay on this page and are never sent or saved.
sudo -u postgres createuser --pwprompt {{dbRole}}; sudo -u postgres createdb --owner={{dbRole}} --encoding=UTF8 --template=template0 {{dbName}}; sudo -u postgres psql -d postgres -c "SELECT datname, pg_encoding_to_char(encoding) AS encoding, datcollate, datctype FROM pg_database WHERE datname='{{dbName}}';"; sudo -u postgres psql -d postgres -c "SELECT rolname, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole FROM pg_roles WHERE rolname='{{dbRole}}';"Enter password for new role: Enter it again: datname | encoding | datcollate | datctype -------------+----------+------------+---------- nextcloud | UTF8 | C.UTF-8 | C.UTF-8 rolname | rolcanlogin | rolsuper | rolcreatedb | rolcreaterole -----------+-------------+----------+-------------+--------------- nextcloud | t | f | f | f
Checkpoint: Checkpoint: Create a least-privilege PostgreSQL database and role
Continue whenDedicated database is UTF-8 and owned by a login role that is not superuser, database/role creator or replication-capable; no secret appears in evidence.
Stop whenRole has elevated attributes, a shared application role is proposed, authentication is broadened, or password cannot be protected and recovered.
If this step fails
The installer or application reports that it cannot connect to PostgreSQL.
Likely causeThe database service is stopped, role/database/host authentication is wrong, `pg_hba.conf` does not match the local design, DNS/socket selection differs, or the password was rotated without updating protected configuration.
systemctl status postgresql --no-pagersudo -u postgres psql -d postgres -c '\l+ nextcloud'sudo -u postgres psql -d postgres -c '\du+ nextcloud'sudo journalctl -u postgresql -n 80 --no-pager
ResolutionRestore the intended local PostgreSQL authentication path, role ownership and protected credential; validate with a least-privilege connection without printing the secret, then retry Nextcloud.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
config
Bind Redis to a protected local Unix socket
Configure the local Redis instance with networking disabled for this single-host profile, a Unix socket under `/run/redis`, and group permissions that allow the web worker but no unrelated user. Add `www-data` to the Redis socket group, restart the service, and prove access as the application identity.
Why this step matters
Nextcloud uses Redis for distributed cache and transactional locking. A local socket avoids an unnecessary TCP listener while filesystem and group permissions define the client boundary.
What to understand
Treat group membership as privilege: audit it and restart long-running PHP-FPM workers after changing supplemental groups.
The socket is recreated at service start; validate the runtime directory and permissions after package updates and reboots.
This profile is intentionally single host. A remote Redis design needs TLS/authentication/network policy and independent availability review.
System changes
- Disables Redis TCP, enables a local Unix socket, grants the web worker socket-group access, and restarts Redis/PHP-FPM.
Syntax explained
port 0- Disables TCP listening for this local-only design.
unixsocketperm 770- Allows only owner/group processes to use the Redis socket.
protected-mode yes- Retains Redis safety behavior in addition to the socket-only boundary.
/etc/redis/redis.conf (reviewed directives)port 0
unixsocket /run/redis/redis-server.sock
unixsocketperm 770
protected-mode yessudo usermod -aG redis www-data; sudo systemctl restart redis-server php8.3-fpm; sudo ss -xl | grep redis-server.sock; sudo -u www-data redis-cli -s /run/redis/redis-server.sock ping; sudo ss -lntp | grep ':6379 ' || trueu_str LISTEN 0 511 /run/redis/redis-server.sock PONG
Checkpoint: Checkpoint: Bind Redis to a protected local Unix socket
Continue when`www-data` receives `PONG` through the exact socket, Redis has no TCP 6379 listener, and PHP-FPM restarts cleanly.
Stop whenSocket becomes world-accessible, unrelated users join the group, TCP remains exposed, or PHP-FPM/Redis does not recover.
If this step fails
Nextcloud reports Redis connection errors or transactional file locking remains unavailable.
Likely causeThe Redis service/socket is absent, the web worker lacks socket group access, `port 0` was set without a Unix socket, or `config.php` points at the wrong path.
systemctl status redis-server --no-pagersudo ss -xl | grep redis-server.socknamei -l /run/redis/redis-server.socksudo -u www-data redis-cli -s /run/redis/redis-server.sock ping
ResolutionRestore the local Unix socket, its runtime permissions and the web-worker group membership, restart affected services, and require `PONG` plus a clean setup check before serving users.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
command
Verify and deploy the official Nextcloud 34.0.2 release
Download Nextcloud 34.0.2 and its checksum from the official release channel over validated TLS, verify before extraction, inspect the archive root, then deploy application code under `/var/www/nextcloud`. Create a dedicated data directory outside the web root. Use narrow ownership and permissions; do not make the complete application or data tree world-writable.
Why this step matters
Checksum verification prevents an incomplete or substituted archive from becoming trusted code. Separating replaceable code from durable data reduces accidental exposure and makes backup/restore boundaries explicit.
What to understand
The SHA256 file is fetched from the official release channel and evidence must identify the exact version; for higher assurance, additionally validate the official signature process.
Writable directories depend on installed apps and update method. Avoid a blanket recursive `www-data` owner unless the documented updater workflow temporarily requires it.
Do not reuse a data directory from another instance without a coordinated database/config restore and explicit fingerprint/cutover process.
System changes
- Downloads and extracts verified Nextcloud application code and creates an empty protected data directory outside the web root.
Syntax explained
--fail- Makes curl fail on HTTP errors rather than saving an error page as an archive.
--proto '=https' --tlsv1.2- Restricts transfer to HTTPS with a minimum TLS version.
sha256sum --check- Compares the downloaded archive against the official release digest before extraction.
Values stay on this page and are never sent or saved.
curl --fail --location --proto '=https' --tlsv1.2 --output {{nextcloudArchive}} https://download.nextcloud.com/server/releases/nextcloud-34.0.2.tar.bz2; curl --fail --location --proto '=https' --tlsv1.2 --output {{nextcloudArchive}}.sha256 https://download.nextcloud.com/server/releases/nextcloud-34.0.2.tar.bz2.sha256; (cd "$(dirname {{nextcloudArchive}})" && sha256sum --check "$(basename {{nextcloudArchive}}).sha256"); tar -tjf {{nextcloudArchive}} | head; sudo tar -xjf {{nextcloudArchive}} -C /var/www; sudo install -d -o www-data -g www-data -m 0750 {{nextcloudData}}; sudo chown -R root:www-data {{nextcloudRoot}}; sudo find {{nextcloudRoot}} -type d -exec chmod 0750 {} +; sudo find {{nextcloudRoot}} -type f -exec chmod 0640 {} +; sudo install -d -o www-data -g www-data -m 0750 {{nextcloudRoot}}/config {{nextcloudRoot}}/custom_apps; stat -c '%A %U:%G %n' {{nextcloudRoot}} {{nextcloudRoot}}/config {{nextcloudData}}nextcloud-34.0.2.tar.bz2: OK nextcloud/ nextcloud/3rdparty/ nextcloud/apps/ drwxr-x--- root:www-data /var/www/nextcloud drwxr-x--- www-data:www-data /var/www/nextcloud/config drwxr-x--- www-data:www-data /srv/nextcloud-data
Checkpoint: Checkpoint: Verify and deploy the official Nextcloud 34.0.2 release
Continue whenOfficial 34.0.2 archive verifies, extracts under one expected root, and application/config/data permissions match the documented write boundary.
Stop whenChecksum/signature cannot be verified, archive contains unexpected paths, data is inside public web root, or permissions expose content broadly.
If this step fails
The downloaded Nextcloud archive fails its checksum or signature verification.
Likely causeThe archive is incomplete, a mirror/proxy altered it, the digest was copied from the wrong release, or the download source is not the official release channel.
sha256sum {{nextcloudArchive}}ls -l {{nextcloudArchive}} {{nextcloudArchive}}.sha256file {{nextcloudArchive}}
ResolutionDelete the untrusted files, obtain the exact release and digest from the official Nextcloud channel over validated TLS, verify again, and retain the evidence. Never install an unverifiable archive.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
config
Publish only the canonical HTTPS service through Nginx
Issue a certificate for the canonical hostname through an approved ACME or organizational CA process, then adapt the official Nextcloud 34 Nginx example. Redirect HTTP to HTTPS, point the document root at application code, pass only real PHP scripts to the correct PHP-FPM socket, deny sensitive directories/files, implement DAV discovery, and set an upload limit justified by capacity and abuse policy.
Why this step matters
The web configuration is part of the security boundary. Correct location ordering prevents source/config/data disclosure while canonical HTTPS and DAV redirects make browsers and synchronization clients behave consistently.
What to understand
Start from the official Nginx sample for the exact Nextcloud release and compare every local deviation after upgrades; this excerpt makes the important boundaries visible but does not supersede release documentation.
Use an externally trusted certificate and test hostname/chain/expiry from outside the host. Do not disable validation in production clients.
If TLS terminates at a separate proxy, replace this topology only with a reviewed trusted-proxy design and protect the proxy-to-origin path.
System changes
- Enables the Nextcloud Nginx virtual host, HTTPS redirect, TLS endpoint, PHP routing, DAV discovery and sensitive-path denials.
Syntax explained
try_files ... =404- Prevents Nginx from forwarding arbitrary nonexistent PHP paths to FPM.
fastcgi_request_buffering off- Allows streaming large request bodies as recommended by the Nextcloud Nginx pattern.
client_max_body_size- Sets an explicit upload ceiling that must align with PHP and any upstream proxy.
/etc/nginx/sites-available/nextcloudValues stay on this page and are never sent or saved.
upstream php-handler {
server unix:/run/php/php8.3-fpm.sock;
}
server {
listen 80;
listen [::]:80;
server_name {{nextcloudFqdn}};
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name {{nextcloudFqdn}};
root {{nextcloudRoot}};
ssl_certificate /etc/letsencrypt/live/{{nextcloudFqdn}}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{nextcloudFqdn}}/privkey.pem;
client_max_body_size {{uploadLimit}};
fastcgi_buffers 64 4K;
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Robots-Tag "noindex, nofollow" always;
index index.php index.html /index.php$request_uri;
location = / { if ( $http_user_agent ~ ^DavClnt ) { return 302 /remote.php/webdav/$is_args$args; } }
location = /robots.txt { allow all; log_not_found off; access_log off; }
location ^~ /.well-known { location = /.well-known/carddav { return 301 /remote.php/dav/; } location = /.well-known/caldav { return 301 /remote.php/dav/; } try_files $uri $uri/ =404; }
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
location / { try_files $uri $uri/ /index.php$request_uri; }
location ~ \.php(?:$|/) {
rewrite ^/(?!index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+|.+/richdocumentscode(_arm64)?/proxy) /index.php$request_uri;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass php-handler;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
location ~ \.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac)$ { try_files $uri /index.php$request_uri; expires 6M; access_log off; }
location ~ \.woff2?$ { try_files $uri /index.php$request_uri; expires 7d; access_log off; }
}Values stay on this page and are never sent or saved.
sudo ln -sfn /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud; sudo nginx -t; sudo systemctl reload nginx; curl -I http://{{nextcloudFqdn}}/; curl -sS -o /dev/null -w '%{http_code} %{url_effective}\n' https://{{nextcloudFqdn}}/status.php; curl -sS -o /dev/null -w '%{http_code}\n' https://{{nextcloudFqdn}}/config/config.php; curl -sSI https://{{nextcloudFqdn}}/.well-known/caldav | sed -n '1,5p'nginx: configuration file /etc/nginx/nginx.conf test is successful HTTP/1.1 301 Moved Permanently Location: https://cloud.example.net/ 200 https://cloud.example.net/status.php 404 HTTP/2 301 location: /remote.php/dav/
Checkpoint: Checkpoint: Publish only the canonical HTTPS service through Nginx
Continue whenNginx syntax passes, HTTP redirects once to canonical HTTPS, status endpoint returns 200, sensitive path returns 404, DAV redirects correctly, and external TLS validation passes.
Stop whenCertificate hostname/chain fails, a sensitive path returns content, redirect loops, PHP source is served, wrong socket is used, or external HTTPS differs from local test.
If this step fails
Nginx can serve the data directory, configuration files, hidden files, or source internals.
Likely causeThe version-matched official Nginx deny locations were omitted, document root is wrong, a generic PHP/static location wins, or the data directory was placed below a publicly reachable path.
curl -skI https://{{nextcloudFqdn}}/config/config.phpcurl -skI https://{{nextcloudFqdn}}/data/curl -skI https://{{nextcloudFqdn}}/.htaccesssudo nginx -T
ResolutionMove data outside the web root, restore the official version-matched deny and PHP/static location ordering, reload Nginx, and require 403/404 for every sensitive probe from an external client.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
instruction
Complete the one-time installer without exposing credentials
Open the canonical HTTPS installer only from the protected administration path. Enter a newly generated Nextcloud administrator secret and the separately protected PostgreSQL credential interactively; choose PostgreSQL, the dedicated role/database, local database endpoint, and the external data directory. Close public installer access immediately by completing installation, then verify through `occ`. Never place either password in a URL, command argument, shell history, evidence, or automation log.
Why this step matters
The installer must receive two powerful credentials, but command-line installation commonly exposes them in process arguments. A protected HTTPS session keeps them out of argv while `occ` supplies reproducible, non-secret postconditions.
What to understand
Use unique named administrator accounts and create a second recovery administrator only under documented break-glass control; do not share the installation account.
The database password remains in protected application configuration by necessity. Restrict file access and use `occ config:list system --private` when collecting evidence.
Do not enable optional apps until the base service, cron, cache, backup and recovery are proven.
System changes
- Initializes the Nextcloud database/schema and protected configuration and creates the first named administrator.
Syntax explained
PostgreSQL- Uses the dedicated PostgreSQL 16 role and database prepared in the prior checkpoint.
external data directory- Stores durable user content outside the Nginx document root.
interactive secret entry- Avoids credential exposure in process listings and shell history.
Values stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ status; sudo -u www-data php {{nextcloudRoot}}/occ config:system:get version; sudo -u www-data php {{nextcloudRoot}}/occ config:system:get datadirectory; sudo -u www-data php {{nextcloudRoot}}/occ user:list --output=json | php -r '$d=json_decode(stream_get_contents(STDIN),true); echo count($d), PHP_EOL;'- installed: true - version: 34.0.2.1 - versionstring: 34.0.2 - maintenance: false - needsDbUpgrade: false 34.0.2.1 /srv/nextcloud-data 1
Checkpoint: Checkpoint: Complete the one-time installer without exposing credentials
Continue when`occ status` reports installed 34.0.2, maintenance false and no database upgrade; data directory is exact; only the intended initial administrator exists.
Stop whenInstaller is reachable over untrusted transport, any secret appears in argv/log/evidence, version or data path differs, DB upgrade is pending, or unexpected users/apps exist.
If this step fails
The installer or application reports that it cannot connect to PostgreSQL.
Likely causeThe database service is stopped, role/database/host authentication is wrong, `pg_hba.conf` does not match the local design, DNS/socket selection differs, or the password was rotated without updating protected configuration.
systemctl status postgresql --no-pagersudo -u postgres psql -d postgres -c '\l+ nextcloud'sudo -u postgres psql -d postgres -c '\du+ nextcloud'sudo journalctl -u postgresql -n 80 --no-pager
ResolutionRestore the intended local PostgreSQL authentication path, role ownership and protected credential; validate with a least-privilege connection without printing the secret, then retry Nextcloud.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
config
Set trusted identity, cache, locking, and maintenance policy
Configure the canonical trusted domain, optional exact proxy addresses, CLI URL, maintenance window, local APCu cache, Redis distributed cache and Redis transactional file locking. Apply values through `occ config:system:set` or a protected reviewed configuration process, then display only private-redacted configuration. Do not use broad proxy ranges or copy a generic `config.php` containing another deployment's secrets.
Why this step matters
Trusted identity prevents Host-header confusion; local/distributed caches reduce database pressure; Redis locking protects concurrent file operations. Private-redacted inspection proves configuration without leaking credentials.
What to understand
If a reverse proxy is used, add only its stable source addresses to `trusted_proxies` and ensure it overwrites rather than appends untrusted forwarded headers.
APCu must be enabled for the web runtime; CLI may need `apc.enable_cli=1` only for relevant administrative jobs after review.
The maintenance window indicates when non-time-sensitive daily work should run; it does not independently schedule system cron.
System changes
- Updates Nextcloud system configuration for hostname, CLI URL, maintenance window, APCu, Redis and file locking.
Syntax explained
memcache.local- Uses APCu inside each PHP process for local cache.
memcache.distributed- Uses Redis for cache shared across application requests.
memcache.locking- Uses Redis transactional file locking to avoid database-based locking contention.
{{nextcloudRoot}}/config/config.php (non-secret reviewed excerpt)Values stay on this page and are never sent or saved.
'trusted_domains' => ['{{nextcloudFqdn}}'],
'overwrite.cli.url' => 'https://{{nextcloudFqdn}}',
'maintenance_window_start' => 2,
'memcache.local' => '\\OC\\Memcache\\APCu',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' => [
'host' => '/run/redis/redis-server.sock',
'port' => 0,
'timeout' => 1.5,
],Values stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ config:system:set trusted_domains 0 --value={{nextcloudFqdn}}; sudo -u www-data php {{nextcloudRoot}}/occ config:system:set overwrite.cli.url --value=https://{{nextcloudFqdn}}; sudo -u www-data php {{nextcloudRoot}}/occ config:system:set maintenance_window_start --type=integer --value=2; sudo -u www-data php {{nextcloudRoot}}/occ config:system:set memcache.local --value='\\OC\\Memcache\\APCu'; sudo -u www-data php {{nextcloudRoot}}/occ config:system:set memcache.distributed --value='\\OC\\Memcache\\Redis'; sudo -u www-data php {{nextcloudRoot}}/occ config:system:set memcache.locking --value='\\OC\\Memcache\\Redis'; sudo -u www-data php {{nextcloudRoot}}/occ config:list system --private; sudo -u www-data redis-cli -s /run/redis/redis-server.sock pingSystem config value trusted_domains => 0 set to string cloud.example.net
System config value maintenance_window_start set to integer 2
{
"system": {
"trusted_domains": ["cloud.example.net"],
"dbpassword": "***REMOVED SENSITIVE VALUE***",
"memcache.locking": "\\OC\\Memcache\\Redis"
}
}
PONGCheckpoint: Checkpoint: Set trusted identity, cache, locking, and maintenance policy
Continue whenRedacted config contains only canonical trust, intended caches/socket and maintenance window; Redis answers as `www-data`; setup check has no cache/locking warning.
Stop whenSecrets appear in output, wildcard/broad proxy trust is proposed, Redis fails, or configuration syntax/application health regresses.
If this step fails
Nextcloud reports Redis connection errors or transactional file locking remains unavailable.
Likely causeThe Redis service/socket is absent, the web worker lacks socket group access, `port 0` was set without a Unix socket, or `config.php` points at the wrong path.
systemctl status redis-server --no-pagersudo ss -xl | grep redis-server.socknamei -l /run/redis/redis-server.socksudo -u www-data redis-cli -s /run/redis/redis-server.sock ping
ResolutionRestore the local Unix socket, its runtime permissions and the web-worker group membership, restart affected services, and require `PONG` plus a clean setup check before serving users.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
config
Run background jobs predictably with systemd
Set Nextcloud background-job mode to Cron and create a root-managed systemd oneshot service that executes `occ system:cron` as `www-data`, plus a persistent five-minute timer with randomized delay. Do not run application cron as root or rely on user page loads. Verify multiple invocations and investigate every failure rather than accepting an old last-run timestamp.
Why this step matters
Email, cleanup, previews, federation and maintenance depend on regular background jobs. A visible timer is more observable and resilient than AJAX, while the application identity preserves the file/config boundary.
What to understand
A successful oneshot service is normally inactive after completion; inspect its exit status and journal instead of expecting `active (running)`.
Persistent timers catch up after downtime. Randomized delay avoids synchronized load when many hosts share the same schedule.
Alert on repeated service failure and background-job age, not merely timer enabled state.
System changes
- Sets application background mode to Cron and enables a five-minute systemd timer/service running as `www-data`.
Syntax explained
User=www-data- Runs `occ` under the same service identity that owns writable application state.
Persistent=true- Runs a missed timer after the host becomes available.
RandomizedDelaySec- Adds a small bounded jitter to reduce synchronized maintenance spikes.
/etc/systemd/system/nextcloud-cron.service and nextcloud-cron.timerValues stay on this page and are never sent or saved.
[Unit]
Description=Nextcloud background jobs
After=postgresql.service redis-server.service
[Service]
Type=oneshot
User=www-data
Group=www-data
ExecStart=/usr/bin/php -f {{nextcloudRoot}}/occ system:cron
# nextcloud-cron.timer
[Unit]
Description=Run Nextcloud background jobs every five minutes
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
RandomizedDelaySec=30s
Persistent=true
[Install]
WantedBy=timers.targetValues stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ background:cron; sudo systemctl daemon-reload; sudo systemctl enable --now nextcloud-cron.timer; sudo systemctl start nextcloud-cron.service; systemctl list-timers nextcloud-cron.timer --all; systemctl status nextcloud-cron.service --no-pager; journalctl -u nextcloud-cron.service -n 20 --no-pagerSet mode for background jobs to 'cron' NEXT LEFT LAST PASSED UNIT Tue 2026-07-28 10:00:14 CEST 4min Tue 2026-07-28 09:55:08 CEST 55s ago nextcloud-cron.timer Active: inactive (dead) since Tue 2026-07-28 09:55:09 CEST; 1s ago Finished Nextcloud background jobs.
Checkpoint: Checkpoint: Run background jobs predictably with systemd
Continue whenCron mode is selected, timer is enabled with recent/next run, oneshot exits successfully as `www-data`, and the administration setup check sees fresh jobs.
Stop whenService runs as root, wrong PHP/root is used, overlap/failure repeats, or users are served while required background work is stale.
If this step fails
Cron background jobs are hours old or the administration page recommends AJAX.
Likely causeThe systemd timer is disabled, the service runs as root or another user, the PHP path/root is wrong, a prior job is still failing, or background-job mode is not `cron`.
systemctl status nextcloud-cron.timer --no-pagersystemctl list-timers nextcloud-cron.timer --alljournalctl -u nextcloud-cron.service -n 80 --no-pagersudo -u www-data php {{nextcloudRoot}}/occ background:cron
ResolutionCorrect the timer and service to execute `occ system:cron` as `www-data`, enable the timer, set Cron mode, resolve the first application error, and prove repeated successful invocations.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
verification
Prove application, DAV, TLS, cache, and security health
Run Nextcloud status and setup checks, inspect application errors, validate Nginx and certificates externally, test DAV discovery, then use a synthetic least-privilege account to upload, download, share and delete a non-sensitive proof file. Record exact timestamps and hashes. An HTTP 200 alone is not evidence that database, locking, background jobs, storage and synchronization are healthy.
Why this step matters
Layered checks detect failures hidden by a login page. A synthetic file transaction proves the complete path while keeping personal content out of operational evidence.
What to understand
Use a dedicated synthetic monitoring identity with minimal quota/permissions and an app password held by the monitoring secret store.
Run the Nextcloud security scanner from the approved official service only if organizational policy permits its externally visible checks.
Alert on maintenance/upgrade flags, certificate expiry, setup-check regression, database/Redis errors, cron age, storage thresholds and failed synthetic transaction.
System changes
- Reads health and performs a reversible synthetic application transaction; no production user content is inspected.
Syntax explained
occ setupchecks- Runs server-side configuration checks surfaced by Nextcloud administration.
status.php- Returns minimal machine-readable instance state without authenticating as a user.
-verify_return_error- Makes OpenSSL stop on certificate verification failure rather than silently continuing.
Values stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ status; sudo -u www-data php {{nextcloudRoot}}/occ setupchecks; sudo -u www-data php {{nextcloudRoot}}/occ log:tail --lines=40; curl -sS https://{{nextcloudFqdn}}/status.php | php -m json.tool; curl -sSI https://{{nextcloudFqdn}}/.well-known/carddav | sed -n '1,6p'; openssl s_client -connect {{nextcloudFqdn}}:443 -servername {{nextcloudFqdn}} -verify_return_error </dev/null 2>&1 | grep -E 'Verification|Verify return code|subject=|issuer=' - installed: true
- versionstring: 34.0.2
- maintenance: false
- needsDbUpgrade: false
All checks passed.
{
"installed": true,
"maintenance": false,
"needsDbUpgrade": false,
"version": "34.0.2.1"
}
HTTP/2 301
location: /remote.php/dav/
Verification: OK
Verify return code: 0 (ok)Checkpoint: Checkpoint: Prove application, DAV, TLS, cache, and security health
Continue whenVersion 34.0.2 installed, maintenance/upgrade false, setup checks pass, DAV redirects correctly, TLS verifies, logs are clean and synthetic file lifecycle/hash succeeds.
Stop whenAny setup warning is unexplained, logs show recurring errors, TLS/DAV differs externally, or synthetic upload/download hash and cleanup fail.
If this step fails
WebDAV discovery, calendar, contacts, or federation checks fail although the login page loads.
Likely causeThe `.well-known` redirects, front-controller routing, HTTPS hostname, reverse proxy, or Nginx location order differs from the official configuration.
curl -skI https://{{nextcloudFqdn}}/.well-known/caldavcurl -skI https://{{nextcloudFqdn}}/.well-known/carddavcurl -skI https://{{nextcloudFqdn}}/remote.php/dav/sudo nginx -T
ResolutionRestore the version-matched official `.well-known` and front-controller locations, keep redirects on the canonical HTTPS hostname, reload, and test DAV with a synthetic least-privilege account.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
command
Capture one complete, immutable backup generation
Preflight the independent destination, database archive tools, application state and protected database authentication. Enable maintenance mode for the consistency window; capture PostgreSQL in custom format plus application configuration, data, custom applications and themes with ACLs/xattrs; create hashes and a structured manifest; then disable maintenance only after all components validate. Use peer authentication or a root-owned mode-0600 `.pgpass` supplied by the secrets process—never print it or put a password in command arguments.
Why this step matters
Nextcloud recovery needs the database, configuration, data and installed custom/theme state from one logical point. Maintenance mode prevents writes from crossing component copies; hashes detect corruption but do not prove semantic restore.
What to understand
The official backup set includes config, data, database and themes. Preserve custom app code/version when it is not reproducibly redeployed from a verified artifact.
Store the generation off-host on access-restricted immutable or append-only storage, encrypt it, copy offsite, test retention, and protect keys separately.
`--delete` makes a generation mirror its source and can remove destination files. Use only a newly allocated, exact generation directory after two-person destination verification.
For very large deployments, use a documented database/storage snapshot strategy that establishes an equivalent common consistency point; prove it by restore.
System changes
- Temporarily blocks user writes and creates an independent full backup generation plus manifests; production returns to normal after validation.
Syntax explained
pg_dump --format=custom- Creates a portable PostgreSQL archive whose contents can be listed and selectively/restorably processed.
rsync -Aax --numeric-ids- Preserves ACLs, attributes, filesystem boundaries and numeric identities for application/data trees.
--delete- Makes the exact new destination mirror the source; dangerous if the destination variable is wrong.
Values stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ maintenance:mode --on; sudo -u {{backupUser}} pg_dump --format=custom --no-owner --file={{backupSet}}/nextcloud.pgdump {{dbName}}; sudo rsync -Aax --numeric-ids --delete {{nextcloudRoot}}/config/ {{backupSet}}/config/; sudo rsync -Aax --numeric-ids --delete {{nextcloudRoot}}/custom_apps/ {{backupSet}}/custom_apps/; sudo rsync -Aax --numeric-ids --delete {{nextcloudRoot}}/themes/ {{backupSet}}/themes/; sudo rsync -Aax --numeric-ids --delete {{nextcloudData}}/ {{backupSet}}/data/; sudo -u {{backupUser}} pg_restore --list {{backupSet}}/nextcloud.pgdump > {{backupSet}}/nextcloud.pgdump.list; sudo find {{backupSet}} -type f ! -name MANIFEST.sha256 -print0 | sudo sort -z | sudo xargs -0 sha256sum | sudo tee {{backupSet}}/MANIFEST.sha256 >/dev/null; sudo sha256sum -c {{backupSet}}/MANIFEST.sha256; sudo -u www-data php {{nextcloudRoot}}/occ maintenance:mode --off; sudo -u www-data php {{nextcloudRoot}}/occ statusMaintenance mode enabled pg_dump: last built-in OID is 16383 sent 184,482,118,272 bytes received 2,118,442 bytes 406,533,158.31 bytes/sec config/config.php: OK data/appdata_ocabc123/filecache/9/0/1: OK nextcloud.pgdump: OK Maintenance mode disabled - installed: true - maintenance: false - needsDbUpgrade: false
Checkpoint: Checkpoint: Capture one complete, immutable backup generation
Continue whenMaintenance window is recorded; database/config/data/custom apps/themes belong to one generation; hashes and archive listing pass; off-host copy/key/retention are current; service returns healthy.
Stop whenDestination is ambiguous or inside same failure domain, secret protection fails, maintenance mode cannot be established, source changes during copy, any hash/archive check fails, or backup capacity/retention is insufficient.
If this step fails
Backup completed, but the restored instance has missing files, apps, themes, or inconsistent database state.
Likely causeThe database, config, data, custom app/theme, encryption keys, or external storage metadata were captured at different points, maintenance mode was not used, or an incomplete rsync source was selected.
find {{backupSet}} -maxdepth 2 -type f -name MANIFEST.sha256 -printsha256sum -c {{backupSet}}/MANIFEST.sha256sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u postgres pg_restore --list {{backupSet}}/nextcloud.pgdump | head
ResolutionPreserve the failed evidence, recapture all required components as one maintenance-window generation, validate manifests and PostgreSQL archive listing, then repeat an isolated application restore.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- For object/external storage, use the provider's officially supported consistent backup mechanism together with the exact matching Nextcloud database/config generation.
- Use storage snapshots only when database and filesystem consistency are coordinated and the snapshot is exported independently and restore-tested.
Stop conditions
- Never disable maintenance merely to hide an incomplete backup; first establish whether application and database are consistent.
- Never collect or display `.pgpass`, database passwords, user data, encryption keys or recovery keys in backup logs.
verification
Restore into an isolated hostname, database, and data path
Provision an isolated host/network with a different FQDN, empty PostgreSQL database/role, empty root and data paths, and no production client route. Verify the selected generation and decryption key, restore files with ACLs/xattrs, restore PostgreSQL custom archive, adapt only environment-specific protected configuration, then run repair and `maintenance:data-fingerprint`. Test login, file hashes, DAV, sharing, installed apps and a synthetic transaction before any cutover decision.
Why this step matters
An isolated restore is the only reliable proof that every backup component, protected credential, database archive, permission, app dependency and human step works. A fresh data fingerprint tells sync clients the server-side state changed after restore.
What to understand
The restore database and filesystem targets must be empty and unmistakably non-production. Require two-person target/FQDN/network verification.
Preserve the original generation read-only. A failed restore is evidence; do not mutate it while debugging.
Do not run `files:scan --all` automatically. First prove database/data generation consistency and scan only a synthetic scoped path when official recovery diagnosis calls for it.
If server-side encryption is enabled, separately verify recovery keys and encryption metadata before declaring any user file readable.
A cutover requires separate DNS, client communication, identity, mail, federation, monitoring and rollback approval; this drill does not authorize it.
System changes
- Creates a separate restored Nextcloud instance and database in an isolated environment; production data and clients remain untouched.
Syntax explained
--clean --if-exists- Drops matching objects in the confirmed isolated target to make the database archive authoritative.
--no-owner --role- Restores objects under the dedicated isolated application role instead of production ownership.
maintenance:data-fingerprint- Changes the restored instance fingerprint so clients can reconcile a server-side restore safely.
Values stay on this page and are never sent or saved.
sudo sha256sum -c {{backupSet}}/MANIFEST.sha256; sudo rsync -Aax --numeric-ids {{backupSet}}/config/ {{restoreRoot}}/config/; sudo rsync -Aax --numeric-ids {{backupSet}}/custom_apps/ {{restoreRoot}}/custom_apps/; sudo rsync -Aax --numeric-ids {{backupSet}}/themes/ {{restoreRoot}}/themes/; sudo rsync -Aax --numeric-ids {{backupSet}}/data/ {{restoreData}}/; sudo -u {{restoreDbUser}} pg_restore --clean --if-exists --no-owner --role={{restoreDbRole}} --dbname={{restoreDbName}} {{backupSet}}/nextcloud.pgdump; sudo -u www-data php {{restoreRoot}}/occ maintenance:repair; sudo -u www-data php {{restoreRoot}}/occ maintenance:data-fingerprint; sudo -u www-data php {{restoreRoot}}/occ status; curl -sS https://{{restoreFqdn}}/status.php | php -m json.toolconfig/config.php: OK
nextcloud.pgdump: OK
WARNING: errors ignored on restore: 0
Nextcloud or one of the apps require upgrade - only a limited number of commands are available
Repair step: Repair mime types
Updated data-fingerprint to 7e2b6dd9c5e7b3e9
- installed: true
- versionstring: 34.0.2
- maintenance: false
- needsDbUpgrade: false
{
"installed": true,
"maintenance": false,
"version": "34.0.2.1"
}Checkpoint: Checkpoint: Restore into an isolated hostname, database, and data path
Continue whenExact generation restores without ignored errors; version/apps/config/data align; fingerprint changes; test account files/hashes, DAV, share and transaction pass within RPO/RTO; production unchanged.
Stop whenTarget can reach production clients, FQDN/database/path is ambiguous, hashes fail, key/app missing, restore reports errors, version mismatch, user file hash/share/DAV fails, or cutover is being assumed.
If this step fails
The isolated restore starts, but desktop clients see widespread conflicts or repeat downloads.
Likely causeThe restored database and data are from different generations, `maintenance:data-fingerprint` was omitted, the restore leaked onto production DNS/network, or clients connected before acceptance.
sudo -u www-data php {{restoreRoot}}/occ statussudo -u www-data php {{restoreRoot}}/occ config:system:get data-fingerprintsudo -u www-data php {{restoreRoot}}/occ files:scan --path='{{testUser}}/files/restore-proof'ip route; getent hosts {{restoreFqdn}}
ResolutionDisconnect the restore from production clients, repeat from one consistent backup generation, run `maintenance:data-fingerprint`, validate a scoped synthetic user before any broad scan, and release only under a documented cutover.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Never point production DNS, desktop clients, mail or federation at the restore during the drill.
- Never overwrite a production database or data directory. Destroy and recreate the isolated target after a failed attempt.
instruction
Rehearse supported updates and complete service recovery
Before every update, read the official release and upgrade notes, confirm the supported sequential path, runtime/database requirements, app compatibility, maintenance window and full restorable generation. Update one release boundary at a time through the supported updater, then repeat status, database, Redis, cron, DAV, TLS and synthetic file checks. At least twice yearly, rebuild an isolated host from the approved baseline and restore the complete service generation, including protected credentials and monitoring.
Why this step matters
Application upgrades can change code, database schema and app contracts together. A full pre-upgrade generation and rehearsed rebuild prevent an incomplete updater from becoming an improvised, destructive recovery.
What to understand
Never skip unsupported major-version transitions. Follow the official sequential upgrade path and current PHP/database matrix.
Disable or upgrade incompatible apps before schema migration. Preserve their data and reviewed code artifact for rollback.
Package and archive installs use different supported updater mechanics; document one method and prevent configuration management from overwriting an in-progress update.
Recovery acceptance includes HTTPS/DAV, authentication, file hashes, sharing, apps, background jobs, outbound notifications and monitoring—not just `occ status`.
System changes
- Updates application code/schema only in an approved window; the separate recovery drill rebuilds an isolated host and complete service.
Syntax explained
updater.phar- Performs the supported archive-install code update workflow; package deployments must use their package method.
occ upgrade- Applies required database/app migrations under the application identity.
one release boundary- Prevents unsupported skipped-major upgrades and makes rollback evidence unambiguous.
Values stay on this page and are never sent or saved.
sudo -u www-data php {{nextcloudRoot}}/occ status; sudo -u www-data php {{nextcloudRoot}}/occ app:list; sudo -u www-data php {{nextcloudRoot}}/updater/updater.phar --no-interaction; sudo -u www-data php {{nextcloudRoot}}/occ upgrade; sudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair; sudo -u www-data php {{nextcloudRoot}}/occ status; systemctl --failed; systemctl list-timers nextcloud-cron.timer --allCurrent version is 34.0.2. Update of code successful. Updater run in non-interactive mode. Nextcloud is already latest version - installed: true - maintenance: false - needsDbUpgrade: false 0 loaded units listed. Tue 2026-07-28 11:20:14 CEST 4min nextcloud-cron.timer
Checkpoint: Checkpoint: Rehearse supported updates and complete service recovery
Continue whenPre-update restore point is proven, supported path completes, maintenance/upgrade flags clear, apps and dependencies pass, all operational checks pass, and isolated full rebuild meets RPO/RTO.
Stop whenNo current restore proof, unsupported version jump, incompatible critical app, insufficient disk/database headroom, migration error, maintenance cannot clear safely, or recovery drill misses acceptance.
If this step fails
An upgrade completes partially or an app is incompatible with Nextcloud 34.
Likely causeThe release path skipped a major version, unsupported PHP/PostgreSQL/app versions remain, maintenance prechecks were ignored, or files/database were changed outside the supported updater.
sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u www-data php {{nextcloudRoot}}/occ app:listsudo -u www-data php {{nextcloudRoot}}/occ maintenance:repairsudo -u www-data php {{nextcloudRoot}}/occ log:tail --lines=80
ResolutionKeep maintenance mode enabled, follow the official sequential upgrade path, disable or update incompatible apps, restore the complete pre-upgrade generation if required, and never mix old application files with a migrated database.
Security notes
- Use named, least-privilege administrative identities over a protected management path. Keep passwords, API tokens, private keys, backup encryption keys, recovery material, and user data out of command arguments, logs, screenshots, tickets, and this guide.
- Preserve an exact before-state and validate the effective result from a real guest, client, or restore target. A successful local command is not end-to-end proof.
Alternatives
- Rehearse this step on an isolated canary with production-shaped hardware, network, storage, identity, data, monitoring, and recovery before production.
Stop conditions
- Stop when hardware, identity, DNS, time, storage, network, certificate, backup, version, or ownership evidence differs from the approved plan.
- Stop when independent console/recovery, current backup, restore capacity, or a named rollback owner is unavailable.
Finish line
Verification checklist
sudo -u www-data php {{nextcloudRoot}}/occ status; sudo -u www-data php {{nextcloudRoot}}/occ setupchecks; curl -sS https://{{nextcloudFqdn}}/status.php; curl -sSI https://{{nextcloudFqdn}}/.well-known/caldav | headNextcloud 34.0.2 healthy, maintenance/upgrade false, setup checks clean, status HTTPS and DAV discovery correct.sudo -u postgres psql -d postgres -c "SELECT rolname,rolsuper,rolcreatedb FROM pg_roles WHERE rolname='{{dbRole}}';"; sudo -u www-data redis-cli -s /run/redis/redis-server.sock ping; systemctl list-timers nextcloud-cron.timer --allDedicated role has no elevated attributes, Redis socket returns PONG with no TCP listener, and Cron has recent/next successful run.sudo sha256sum -c {{backupSet}}/MANIFEST.sha256; sudo -u {{backupUser}} pg_restore --list {{backupSet}}/nextcloud.pgdump | head; find {{backupSet}} -maxdepth 1 -type d -printf '%f\n'Hash and PostgreSQL archive checks pass and config, data, custom apps and themes are present in one independent generation.sudo -u www-data php {{restoreRoot}}/occ status; curl -sS https://{{restoreFqdn}}/status.php; sudo -u www-data php {{restoreRoot}}/occ config:system:get data-fingerprintIsolated exact generation passes version, files/hash, DAV, shares, apps and synthetic transaction with a new fingerprint inside RPO/RTO.Recovery guidance
Common problems and safe checks
The browser shows 502 Bad Gateway after the Nginx configuration is enabled.
Likely causeNginx points at the wrong PHP-FPM socket, PHP-FPM is stopped, the pool name differs, or Unix-socket permissions do not permit the web worker to connect.
systemctl status nginx php8.3-fpm --no-pagersudo ss -xl | grep phpsudo nginx -T | grep -n fastcgi_passsudo journalctl -u nginx -u php8.3-fpm -n 80 --no-pager
ResolutionMatch `fastcgi_pass` to the active PHP 8.3 pool socket, correct the supported service configuration, reload PHP-FPM and Nginx, and repeat both the local and external HTTPS checks.
Nextcloud rejects the request with an untrusted-domain message.
Likely causeThe canonical hostname is absent from `trusted_domains`, a proxy presents a different host, or users are following an unapproved alias or address.
sudo -u www-data php {{nextcloudRoot}}/occ config:system:get trusted_domainscurl -skI --resolve {{nextcloudFqdn}}:443:127.0.0.1 https://{{nextcloudFqdn}}/status.phpsudo nginx -T | grep -nE 'server_name|proxy_set_header Host'
ResolutionAdd only the reviewed canonical hostname through `occ config:system:set trusted_domains`, correct the proxy Host header if one is deliberately used, and reject undocumented aliases rather than trusting broad wildcards.
HTTPS redirects loop or generated links use HTTP behind a reverse proxy.
Likely causeThe proxy is not trusted, forwarded headers are accepted from an untrusted source, or `overwriteprotocol`, `overwritehost`, and the public URL do not describe the actual external path.
curl -IL https://{{nextcloudFqdn}}/loginsudo -u www-data php {{nextcloudRoot}}/occ config:list system --privatesudo nginx -T | grep -nE 'real_ip|X-Forwarded|server_name'
ResolutionTrust only the exact proxy addresses, set overwrite values only when the documented proxy design requires them, sanitize forwarded headers at the trusted proxy, and verify the full external redirect chain.
The installer or application reports that it cannot connect to PostgreSQL.
Likely causeThe database service is stopped, role/database/host authentication is wrong, `pg_hba.conf` does not match the local design, DNS/socket selection differs, or the password was rotated without updating protected configuration.
systemctl status postgresql --no-pagersudo -u postgres psql -d postgres -c '\l+ nextcloud'sudo -u postgres psql -d postgres -c '\du+ nextcloud'sudo journalctl -u postgresql -n 80 --no-pager
ResolutionRestore the intended local PostgreSQL authentication path, role ownership and protected credential; validate with a least-privilege connection without printing the secret, then retry Nextcloud.
Nextcloud reports Redis connection errors or transactional file locking remains unavailable.
Likely causeThe Redis service/socket is absent, the web worker lacks socket group access, `port 0` was set without a Unix socket, or `config.php` points at the wrong path.
systemctl status redis-server --no-pagersudo ss -xl | grep redis-server.socknamei -l /run/redis/redis-server.socksudo -u www-data redis-cli -s /run/redis/redis-server.sock ping
ResolutionRestore the local Unix socket, its runtime permissions and the web-worker group membership, restart affected services, and require `PONG` plus a clean setup check before serving users.
Cron background jobs are hours old or the administration page recommends AJAX.
Likely causeThe systemd timer is disabled, the service runs as root or another user, the PHP path/root is wrong, a prior job is still failing, or background-job mode is not `cron`.
systemctl status nextcloud-cron.timer --no-pagersystemctl list-timers nextcloud-cron.timer --alljournalctl -u nextcloud-cron.service -n 80 --no-pagersudo -u www-data php {{nextcloudRoot}}/occ background:cron
ResolutionCorrect the timer and service to execute `occ system:cron` as `www-data`, enable the timer, set Cron mode, resolve the first application error, and prove repeated successful invocations.
The downloaded Nextcloud archive fails its checksum or signature verification.
Likely causeThe archive is incomplete, a mirror/proxy altered it, the digest was copied from the wrong release, or the download source is not the official release channel.
sha256sum {{nextcloudArchive}}ls -l {{nextcloudArchive}} {{nextcloudArchive}}.sha256file {{nextcloudArchive}}
ResolutionDelete the untrusted files, obtain the exact release and digest from the official Nextcloud channel over validated TLS, verify again, and retain the evidence. Never install an unverifiable archive.
Nginx can serve the data directory, configuration files, hidden files, or source internals.
Likely causeThe version-matched official Nginx deny locations were omitted, document root is wrong, a generic PHP/static location wins, or the data directory was placed below a publicly reachable path.
curl -skI https://{{nextcloudFqdn}}/config/config.phpcurl -skI https://{{nextcloudFqdn}}/data/curl -skI https://{{nextcloudFqdn}}/.htaccesssudo nginx -T
ResolutionMove data outside the web root, restore the official version-matched deny and PHP/static location ordering, reload Nginx, and require 403/404 for every sensitive probe from an external client.
Large uploads return 413, time out, or fail near completion.
Likely causeNginx `client_max_body_size`, PHP upload/post limits, FPM/request timeouts, storage capacity, reverse-proxy limits, or application chunking are inconsistent.
sudo nginx -T | grep -n client_max_body_sizephp -i | grep -E 'upload_max_filesize|post_max_size|max_execution_time'df -h {{nextcloudData}}sudo journalctl -u nginx -u php8.3-fpm -n 100 --no-pager
ResolutionChoose an approved maximum, align every proxy/Nginx/PHP layer with capacity and abuse controls, reload safely, then upload and download a synthetic file at the boundary while monitoring storage and logs.
Backup completed, but the restored instance has missing files, apps, themes, or inconsistent database state.
Likely causeThe database, config, data, custom app/theme, encryption keys, or external storage metadata were captured at different points, maintenance mode was not used, or an incomplete rsync source was selected.
find {{backupSet}} -maxdepth 2 -type f -name MANIFEST.sha256 -printsha256sum -c {{backupSet}}/MANIFEST.sha256sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u postgres pg_restore --list {{backupSet}}/nextcloud.pgdump | head
ResolutionPreserve the failed evidence, recapture all required components as one maintenance-window generation, validate manifests and PostgreSQL archive listing, then repeat an isolated application restore.
Nextcloud remains in maintenance mode after a failed backup, update, or restore.
Likely causeThe workflow exited before its cleanup step, an update is incomplete, permissions prevent `occ`, or an operator disabled maintenance without first resolving database/filesystem inconsistency.
sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u www-data php {{nextcloudRoot}}/occ config:system:get maintenancesudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair --include-expensivesudo journalctl -u php8.3-fpm -u postgresql -n 100 --no-pager
ResolutionKeep users blocked while determining the failed layer, complete or roll back the reviewed operation, run supported repair/checks, and disable maintenance only after the application, database and file evidence agree.
The isolated restore starts, but desktop clients see widespread conflicts or repeat downloads.
Likely causeThe restored database and data are from different generations, `maintenance:data-fingerprint` was omitted, the restore leaked onto production DNS/network, or clients connected before acceptance.
sudo -u www-data php {{restoreRoot}}/occ statussudo -u www-data php {{restoreRoot}}/occ config:system:get data-fingerprintsudo -u www-data php {{restoreRoot}}/occ files:scan --path='{{testUser}}/files/restore-proof'ip route; getent hosts {{restoreFqdn}}
ResolutionDisconnect the restore from production clients, repeat from one consistent backup generation, run `maintenance:data-fingerprint`, validate a scoped synthetic user before any broad scan, and release only under a documented cutover.
WebDAV discovery, calendar, contacts, or federation checks fail although the login page loads.
Likely causeThe `.well-known` redirects, front-controller routing, HTTPS hostname, reverse proxy, or Nginx location order differs from the official configuration.
curl -skI https://{{nextcloudFqdn}}/.well-known/caldavcurl -skI https://{{nextcloudFqdn}}/.well-known/carddavcurl -skI https://{{nextcloudFqdn}}/remote.php/dav/sudo nginx -T
ResolutionRestore the version-matched official `.well-known` and front-controller locations, keep redirects on the canonical HTTPS hostname, reload, and test DAV with a synthetic least-privilege account.
An upgrade completes partially or an app is incompatible with Nextcloud 34.
Likely causeThe release path skipped a major version, unsupported PHP/PostgreSQL/app versions remain, maintenance prechecks were ignored, or files/database were changed outside the supported updater.
sudo -u www-data php {{nextcloudRoot}}/occ statussudo -u www-data php {{nextcloudRoot}}/occ app:listsudo -u www-data php {{nextcloudRoot}}/occ maintenance:repairsudo -u www-data php {{nextcloudRoot}}/occ log:tail --lines=80
ResolutionKeep maintenance mode enabled, follow the official sequential upgrade path, disable or update incompatible apps, restore the complete pre-upgrade generation if required, and never mix old application files with a migrated database.
Reference
Frequently asked questions
Is copying only the data directory enough?
No. Nextcloud recovery requires a matching database, configuration, data and theme/custom application state plus required encryption/recovery keys.
Does a successful checksum mean the backup is recoverable?
No. It proves bytes match the manifest. Only an isolated database/files/application restore and synthetic acceptance prove semantic recovery.
Why not put the database password in `occ maintenance:install`?
Command arguments can appear in shell history, process listings and automation logs. This guide uses protected interactive installation and redacted post-install checks.
Can Redis listen publicly if it has a password?
Not in this profile. A local Unix socket with narrow filesystem permissions removes unnecessary network exposure; distributed designs require a separate authenticated/TLS network review.
Should `files:scan --all` run after every restore?
No. Restore matching database and data first, change the data fingerprint, validate a synthetic/scoped path, and use broad scans only for a diagnosed official recovery case.
Recovery
Rollback
Rollback the smallest reviewed layer: restore the previous Nginx/PHP/Redis configuration, remove an empty failed installation, or restore the entire matching pre-change Nextcloud generation into isolated targets before an approved cutover. Never mix old code/config/data with a migrated database or overwrite production during diagnosis.
- Keep maintenance mode enabled after an incomplete update, backup or restore; preserve logs, database state, application version and exact failed generation.
- For Nginx/PHP/Redis/Cron changes, restore the versioned previous configuration, validate syntax/service/socket, then repeat external TLS/DAV/status and synthetic checks.
- For an application/schema failure, restore database, config, data, custom apps/themes and required keys from the same pre-change generation in an isolated environment.
- Run supported repair and data-fingerprint procedures, prove users/files/shares/DAV/apps/background jobs, then perform a separately approved DNS/client cutover if recovery is required.
- Rotate administrator, database, app-password, TLS, backup or encryption credentials when exposure or provenance is uncertain.
Evidence