OneLinersCommand workbench
Guides
Services & Applications / Databases & Data / Security / Backup & Recovery

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.

360 min14 stepsHigh-impact changeRevision 1
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 14 steps completed
Goal

Operate a secure, maintainable Nextcloud service whose web, database, locking, jobs, data protection, updates, and recovery are verified end to end.

Supported environments
  • Nextcloud Server 34.0.2
  • Ubuntu Server 24.04 LTS
  • PHP 8.3
  • PostgreSQL 16
  • Redis 7.x
Prerequisites
  • 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.
Operating boundary

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

System
  • 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.
Observable outcome
  • 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.

Nginx and TLS identityExpose only the canonical HTTPS service, route PHP/DAV correctly, enforce upload policy and deny sensitive paths.
Nextcloud applicationProvides authenticated file synchronization, sharing, WebDAV and app services while keeping durable data outside web root.
PHP 8.3 FPM and systemd CronExecute web requests and scheduled background jobs as the application identity.
PostgreSQL 16Stores authoritative file metadata, users, shares, app configuration and transactional state under a dedicated role.
APCu and Redis 7Provide local/distributed caching and transactional file locking without a public Redis listener.
Independent backup and isolated restoreProtect all required application generations, keys and manifests and prove recovery away from production clients.
  1. Clients validate the canonical certificate and connect to Nginx; HTTP redirects once to HTTPS while sensitive paths are denied.
  2. Nginx passes allowed front-controller/PHP requests to PHP-FPM, which reads protected Nextcloud configuration and durable data.
  3. Nextcloud commits metadata to the dedicated PostgreSQL database and coordinates cache/locks through APCu plus the Redis Unix socket.
  4. The systemd timer invokes `occ system:cron` as `www-data`; health checks observe status, setup, logs, DAV, TLS, storage and a synthetic file.
  5. A maintenance-window backup captures database, config, data, custom apps and themes as one hashed off-host generation.
  6. 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}}secret

New 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-probe

Security 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.
01

instruction

Freeze the service, security, and recovery design

read-only

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.
Command
Fill variables0/2 ready

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 -dates
Example output / evidence
203.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.

Safe checks
  • curl -IL https://{{nextcloudFqdn}}/login
  • sudo -u www-data php {{nextcloudRoot}}/occ config:list system --private
  • sudo 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.
02

verification

Validate Ubuntu, PHP, PostgreSQL, Redis, and host controls

read-only

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.
Command
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 verbose
Example output / evidence
Ubuntu 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.

Safe checks
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -u www-data php {{nextcloudRoot}}/occ app:list
  • sudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair
  • sudo -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.
03

command

Install the reviewed web and application dependencies

caution

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.
Command
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-server
Example output / evidence
apcu
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.

Safe checks
  • systemctl status nginx php8.3-fpm --no-pager
  • sudo ss -xl | grep php
  • sudo nginx -T | grep -n fastcgi_pass
  • sudo 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.
04

command

Create a least-privilege PostgreSQL database and role

caution

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.
Command
Fill variables0/2 ready

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}}';"
Example output / evidence
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.

Safe checks
  • systemctl status postgresql --no-pager
  • sudo -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.
05

config

Bind Redis to a protected local Unix socket

caution

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.
File /etc/redis/redis.conf (reviewed directives)
Configuration
port 0
unixsocket /run/redis/redis-server.sock
unixsocketperm 770
protected-mode yes
Command
sudo 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 ' || true
Example output / evidence
u_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.

Safe checks
  • systemctl status redis-server --no-pager
  • sudo ss -xl | grep redis-server.sock
  • namei -l /run/redis/redis-server.sock
  • sudo -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.
06

command

Verify and deploy the official Nextcloud 34.0.2 release

caution

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.
Command
Fill variables0/3 ready

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}}
Example output / evidence
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.

Safe checks
  • sha256sum {{nextcloudArchive}}
  • ls -l {{nextcloudArchive}} {{nextcloudArchive}}.sha256
  • file {{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.
07

config

Publish only the canonical HTTPS service through Nginx

caution

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.
File /etc/nginx/sites-available/nextcloud
Configuration
Fill variables0/3 ready

Values 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; }
}
Command
Fill variables0/1 ready

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'
Example output / evidence
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.

Safe checks
  • curl -skI https://{{nextcloudFqdn}}/config/config.php
  • curl -skI https://{{nextcloudFqdn}}/data/
  • curl -skI https://{{nextcloudFqdn}}/.htaccess
  • sudo 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.
08

instruction

Complete the one-time installer without exposing credentials

caution

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.
Command
Fill variables0/1 ready

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;'
Example output / evidence
  - 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.

Safe checks
  • systemctl status postgresql --no-pager
  • sudo -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.
09

config

Set trusted identity, cache, locking, and maintenance policy

caution

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.
File {{nextcloudRoot}}/config/config.php (non-secret reviewed excerpt)
Configuration
Fill variables0/1 ready

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,
],
Command
Fill variables0/2 ready

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 ping
Example output / evidence
System 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"
  }
}
PONG

Checkpoint: 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.

Safe checks
  • systemctl status redis-server --no-pager
  • sudo ss -xl | grep redis-server.sock
  • namei -l /run/redis/redis-server.sock
  • sudo -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.
10

config

Run background jobs predictably with systemd

caution

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.
File /etc/systemd/system/nextcloud-cron.service and nextcloud-cron.timer
Configuration
Fill variables0/1 ready

Values 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.target
Command
Fill variables0/1 ready

Values 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-pager
Example output / evidence
Set 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`.

Safe checks
  • systemctl status nextcloud-cron.timer --no-pager
  • systemctl list-timers nextcloud-cron.timer --all
  • journalctl -u nextcloud-cron.service -n 80 --no-pager
  • sudo -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.
11

verification

Prove application, DAV, TLS, cache, and security health

read-only

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.
Command
Fill variables0/2 ready

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='
Example output / evidence
  - 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.

Safe checks
  • curl -skI https://{{nextcloudFqdn}}/.well-known/caldav
  • curl -skI https://{{nextcloudFqdn}}/.well-known/carddav
  • curl -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.
12

command

Capture one complete, immutable backup generation

caution

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.
Command
Fill variables0/5 ready

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 status
Example output / evidence
Maintenance 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.

Safe checks
  • find {{backupSet}} -maxdepth 2 -type f -name MANIFEST.sha256 -print
  • sha256sum -c {{backupSet}}/MANIFEST.sha256
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -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.
13

verification

Restore into an isolated hostname, database, and data path

danger

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.
Command
Fill variables0/7 ready

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.tool
Example output / evidence
config/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.

Safe checks
  • sudo -u www-data php {{restoreRoot}}/occ status
  • sudo -u www-data php {{restoreRoot}}/occ config:system:get data-fingerprint
  • sudo -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.
14

instruction

Rehearse supported updates and complete service recovery

danger

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.
Command
Fill variables0/1 ready

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 --all
Example output / evidence
Current 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.

Safe checks
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -u www-data php {{nextcloudRoot}}/occ app:list
  • sudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair
  • sudo -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

Application and web boundarysudo -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.
Database, locking and jobssudo -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.
Complete backup generationsudo 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.
Isolated application restoresudo -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.

Safe checks
  • systemctl status nginx php8.3-fpm --no-pager
  • sudo ss -xl | grep php
  • sudo nginx -T | grep -n fastcgi_pass
  • sudo 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.

Safe checks
  • sudo -u www-data php {{nextcloudRoot}}/occ config:system:get trusted_domains
  • curl -skI --resolve {{nextcloudFqdn}}:443:127.0.0.1 https://{{nextcloudFqdn}}/status.php
  • sudo 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.

Safe checks
  • curl -IL https://{{nextcloudFqdn}}/login
  • sudo -u www-data php {{nextcloudRoot}}/occ config:list system --private
  • sudo 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.

Safe checks
  • systemctl status postgresql --no-pager
  • sudo -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.

Safe checks
  • systemctl status redis-server --no-pager
  • sudo ss -xl | grep redis-server.sock
  • namei -l /run/redis/redis-server.sock
  • sudo -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`.

Safe checks
  • systemctl status nextcloud-cron.timer --no-pager
  • systemctl list-timers nextcloud-cron.timer --all
  • journalctl -u nextcloud-cron.service -n 80 --no-pager
  • sudo -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.

Safe checks
  • sha256sum {{nextcloudArchive}}
  • ls -l {{nextcloudArchive}} {{nextcloudArchive}}.sha256
  • file {{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.

Safe checks
  • curl -skI https://{{nextcloudFqdn}}/config/config.php
  • curl -skI https://{{nextcloudFqdn}}/data/
  • curl -skI https://{{nextcloudFqdn}}/.htaccess
  • sudo 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.

Safe checks
  • sudo nginx -T | grep -n client_max_body_size
  • php -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.

Safe checks
  • find {{backupSet}} -maxdepth 2 -type f -name MANIFEST.sha256 -print
  • sha256sum -c {{backupSet}}/MANIFEST.sha256
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -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.

Safe checks
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -u www-data php {{nextcloudRoot}}/occ config:system:get maintenance
  • sudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair --include-expensive
  • sudo 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.

Safe checks
  • sudo -u www-data php {{restoreRoot}}/occ status
  • sudo -u www-data php {{restoreRoot}}/occ config:system:get data-fingerprint
  • sudo -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.

Safe checks
  • curl -skI https://{{nextcloudFqdn}}/.well-known/caldav
  • curl -skI https://{{nextcloudFqdn}}/.well-known/carddav
  • curl -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.

Safe checks
  • sudo -u www-data php {{nextcloudRoot}}/occ status
  • sudo -u www-data php {{nextcloudRoot}}/occ app:list
  • sudo -u www-data php {{nextcloudRoot}}/occ maintenance:repair
  • sudo -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.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Use the officially supported container/appliance or distribution package only when its maintainer owns timely Nextcloud/PHP updates and documents equivalent backup, restore, cache, cron and TLS behavior.
  • Use a dedicated reverse proxy when central certificate and edge policy are required, but trust only exact proxy addresses and protect proxy-to-origin transport.
  • Use a remote PostgreSQL/Redis or multiple app nodes only with independently reviewed TLS/authentication, load balancing, shared storage/object storage, locking, failure handling and consistent backup.
  • Use an external identity provider only after testing emergency local access, account mapping, group/share behavior, certificate rotation and recovery without the provider.

Operate it safely

  • Monitor external TLS/DAV/status, setup-check drift, PHP/Nginx/PostgreSQL/Redis errors, cron age, synthetic file transactions, filesystem/inode/database/cache capacity, backup age/hash/immutability and restore-test age.
  • Review Nextcloud security and maintenance releases, supported PHP/PostgreSQL matrix, app compatibility and official Nginx changes before each sequential upgrade.
  • Run monthly backup integrity/archive checks, quarterly isolated application restores and twice-yearly clean-host rebuilds; record achieved RPO/RTO and remediate every missing credential, app or procedure.
  • Exercise user/admin offboarding, app-password rotation, certificate renewal, database-secret rotation, Redis reboot/socket recovery and server-side encryption key recovery where enabled.
  • Document ransomware response: isolate service, preserve logs/snapshots as evidence, rotate credentials, select a pre-compromise immutable generation and restore without reconnecting affected clients prematurely.

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.

  1. Keep maintenance mode enabled after an incomplete update, backup or restore; preserve logs, database state, application version and exact failed generation.
  2. For Nginx/PHP/Redis/Cron changes, restore the versioned previous configuration, validate syntax/service/socket, then repeat external TLS/DAV/status and synthetic checks.
  3. 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.
  4. 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.
  5. Rotate administrator, database, app-password, TLS, backup or encryption credentials when exposure or provenance is uncertain.

Evidence

Sources and review

Verified 2026-07-24Review due 2026-10-22
Nextcloud official release changelogofficialNextcloud system requirementsofficialNextcloud source installationofficialNextcloud command-line installationofficialNextcloud Nginx configurationofficialNextcloud server hardeningofficialNextcloud caching configurationofficialNextcloud background jobsofficialNextcloud backupofficialNextcloud restoreofficialNextcloud upgradeofficialNextcloud reverse proxy configurationofficial