A Nextcloud instance keeps its state in three places: the data directory, the SQL database, and config/config.php. Back up only one of them and you get an installation that either refuses to start or starts with a file list that no longer matches what is on disk. This tutorial covers a consistent backup of all three parts and a restore you can verify.
What belongs in a Nextcloud backup?
A complete Nextcloud backup consists of three parts: the web root including config/config.php and the apps directory, the data directory with all user files, and a dump of the MySQL, MariaDB or PostgreSQL database.
The three parts are not interchangeable, and each one is useless without the others:
| Component | Typical path | Why it matters |
|---|---|---|
| Web root | /var/www/nextcloud |
Contains the code version, installed apps and themes |
| Configuration | /var/www/nextcloud/config/config.php |
Holds instanceid, passwordsalt, secret and the DB credentials |
| Data directory | /var/www/nextcloud/data |
User files, previews, encryption keys, .ocdata marker |
| Database | nextcloud schema |
File index, shares, users, app state, activity |
If server-side encryption is enabled, the keys in data/files_encryption and the secret value in config.php only work together. Restoring one without the other leaves the files unreadable.
Find out where the data directory actually lives before you start. On many installations it sits outside the web root:
$ sudo -u www-data php /var/www/nextcloud/occ config:system:get datadirectory
/var/www/nextcloud/dataPrerequisites
- A running Nextcloud 25 or newer on a Linux host, for example on a scalable Cloud-VPS
- Root or
sudoaccess, plus the PHP CLI binary used by your installation - The database credentials from
config.php(dbname,dbuser,dbpassword) - A backup target with free space for the full data directory and the SQL dump
rsyncand eithermysqldump(MySQL/MariaDB) orpg_dump(PostgreSQL)
Read the credentials directly from the configuration instead of guessing them:
$ sudo grep -E "dbname|dbuser|dbhost|dbtype" /var/www/nextcloud/config/config.phpWhy does a Nextcloud backup need maintenance mode?
Maintenance mode blocks all client and web access to Nextcloud, so no user can upload a file between the moment the file copy runs and the moment the database dump is written. Without it, the two halves of the backup describe different states.
A desktop client that uploads a 2 GB file during the backup window is enough to produce a database row for a file that never made it into the copy. On restore, Nextcloud lists the file and returns a 404 when someone clicks it.
$ sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --on
Maintenance mode enabledThe order of operations matters. Enable maintenance mode first, copy files, dump the database, then disable maintenance mode:
graph TD A["occ maintenance:mode --on"] --> B["rsync web root and data directory"] A --> C["mysqldump of the nextcloud database"] B --> D["Backup target: /mnt/backup/nextcloud"] C --> D D --> E["occ maintenance:mode --off"] E --> F["Verify with occ status and files:scan"]
Matching infrastructure at centron
No hardware needed to follow along: ccloud³ VMs with full root access, billed by the hour and ready in seconds. Rent a cloud server →
Back up the files
Copy the web root and the data directory with rsync. The flags matter: -A keeps ACLs, -a keeps ownership, timestamps and permissions, -x prevents rsync from crossing into other mounted filesystems.
$ sudo mkdir -p /mnt/backup/nextcloud/$(date +%F)
$ sudo rsync -Aax --delete /var/www/nextcloud/ /mnt/backup/nextcloud/$(date +%F)/nextcloud/If the data directory is mounted separately, back it up as its own target so -x does not silently skip it:
$ sudo rsync -Aax --delete /srv/nextcloud-data/ /mnt/backup/nextcloud/$(date +%F)/data/The --delete flag makes the target an exact mirror of the source. Use it only when each run writes into a dated directory or when you keep separate snapshots on the storage layer. Pointing --delete at a single shared target turns your backup into a live mirror, which propagates deletions instead of protecting against them.
Back up the database
Dump the database while maintenance mode is still active. On MySQL and MariaDB, --single-transaction produces a consistent snapshot of InnoDB tables without locking them:
$ sudo mysqldump --single-transaction --default-character-set=utf8mb4 \
-u nextcloud -p nextcloud > /mnt/backup/nextcloud/$(date +%F)/nextcloud-db.sqlOn PostgreSQL, use pg_dump with the Nextcloud database user:
$ sudo -u postgres pg_dump nextcloud -f /mnt/backup/nextcloud/$(date +%F)/nextcloud-db.sqlCompress the dump afterwards. A 500 MB SQL file typically shrinks below 60 MB, and gzip is fast enough that it does not extend the maintenance window in any meaningful way:
$ sudo gzip /mnt/backup/nextcloud/$(date +%F)/nextcloud-db.sql
$ sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --offAutomate the backup
Put the whole sequence into a script so the maintenance window is always closed, including when a step fails. set -euo pipefail aborts on the first error, and the trap guarantees that maintenance mode is turned off even then.
#!/bin/bash
set -euo pipefail
NC_DIR="/var/www/nextcloud"
DEST="/mnt/backup/nextcloud/$(date +%F)"
OCC="sudo -u www-data php ${NC_DIR}/occ"
DB_NAME="nextcloud"
DB_USER="nextcloud"
DB_PASS="YOUR_DB_PASSWORD"
mkdir -p "${DEST}"
trap '${OCC} maintenance:mode --off' EXIT
${OCC} maintenance:mode --on
rsync -Aax --delete "${NC_DIR}/" "${DEST}/nextcloud/"
mysqldump --single-transaction --default-character-set=utf8mb4 \
-u "${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" > "${DEST}/nextcloud-db.sql"
gzip "${DEST}/nextcloud-db.sql"
find /mnt/backup/nextcloud -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +Store the script as /usr/local/sbin/nextcloud-backup.sh, set the permissions to 0700 so the database password is not world-readable, and schedule it for a low-traffic hour:
$ sudo chmod 700 /usr/local/sbin/nextcloud-backup.sh
$ sudo crontab -e15 2 * * * /usr/local/sbin/nextcloud-backup.sh >> /var/log/nextcloud-backup.log 2>&1The find line at the end of the script keeps 14 daily generations. Adjust -mtime to your retention policy, and copy at least one generation per week to storage that the Nextcloud host cannot write to. A backup that a compromised host can delete is not a backup.
What breaks if you restore only the data directory?
Restoring only the data directory leaves the database holding file entries, share links and user accounts from a different point in time, so Nextcloud lists files that no longer exist on disk and ignores files that do exist.
The file index in the oc_filecache table is authoritative for the web interface and for the sync clients. occ files:scan can rebuild missing entries from disk, but it cannot recreate share links, comments, tags or app data that only existed in the database. Always restore both parts from the same backup run.
Restore Nextcloud from the backup
Restore into the same Nextcloud major version the backup came from. A dump from Nextcloud 28 imported under a Nextcloud 30 code base fails at the schema check, and the upgrade path only runs forward.
- Enable maintenance mode on the target host, or stop the web server if the instance does not start at all.
$ sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --on- Move the current installation aside instead of deleting it. If the restore fails, you still have the broken state to inspect.
$ sudo mv /var/www/nextcloud /var/www/nextcloud-broken- Copy the directories back from the backup:
$ sudo rsync -Aax /mnt/backup/nextcloud/2026-08-16/nextcloud/ /var/www/nextcloud/- Recreate the database and import the dump. Dropping the schema first avoids leftover tables from the newer state:
$ sudo mysql -u root -p -e "DROP DATABASE nextcloud; CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
$ sudo mysql -u root -p -e "GRANT ALL ON nextcloud.* TO 'nextcloud'@'localhost';"
$ zcat /mnt/backup/nextcloud/2026-08-16/nextcloud-db.sql.gz | sudo mysql -u nextcloud -p nextcloud- Fix ownership so PHP-FPM can write to the tree:
$ sudo chown -R www-data:www-data /var/www/nextcloud- Reset the data fingerprint and leave maintenance mode. The fingerprint tells desktop and mobile clients that the server state moved backwards, which makes them prompt for a resync instead of deleting local files:
$ sudo -u www-data php /var/www/nextcloud/occ maintenance:data-fingerprint
$ sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --offVerify the restore
Check the instance status first. A healthy installation reports installed: true and the expected version:
$ sudo -u www-data php /var/www/nextcloud/occ status
- installed: true
- version: 30.0.2.2
- versionstring: 30.0.2
- maintenance: falseThen reconcile the file index with what is actually on disk. On large instances this takes several minutes and prints one line per user:
$ sudo -u www-data php /var/www/nextcloud/occ files:scan --allFinally, log in through the web interface, download a file that was created shortly before the backup, and confirm that an existing share link still resolves. A restore is only verified once file content has actually left the server.
Troubleshooting
"Your data directory is invalid" — the .ocdata marker file is missing from the data directory or the ownership is wrong. Recreate it and reset the owner:
$ sudo touch /var/www/nextcloud/data/.ocdata
$ sudo chown -R www-data:www-data /var/www/nextcloud/dataUnknown collation: 'utf8mb4_0900_ai_ci' — the dump came from MySQL 8 and is being imported into MariaDB, which does not know that collation. Rewrite it during the import:
$ zcat nextcloud-db.sql.gz | sed 's/utf8mb4_0900_ai_ci/utf8mb4_general_ci/g' | sudo mysql -u nextcloud -p nextcloudClients re-download the whole account after a restore — the data fingerprint was not reset, so the clients treat the older server state as a mass deletion. Run occ maintenance:data-fingerprint and let the clients pick up the change on their next sync cycle.
Next steps
The backup script is only half of the procedure. Schedule a restore test into a throwaway host at least quarterly and time it, because the number you need during an incident is how long a full restore takes, not how large the archive is. Keep one generation on storage outside the Nextcloud host, and record the Nextcloud version alongside each backup set so you always know which code base the dump belongs to.
Read next
- Install Nextcloud All-in-One with Docker
- Install Nextcloud on Ubuntu 24.04 with nginx
- Mount Nextcloud via WebDAV on Linux, Windows and macOS
- Nextcloud vs ownCloud vs Seafile: Which One Fits?
- Run Nextcloud with Docker Compose
- Set Up Nextcloud Office: Collabora or OnlyOffice
- Updating Nextcloud: Web Updater, occ and a Rollback Plan
- What Is Nextcloud? Architecture, Components and Use Cases
- Which Server for Nextcloud? Sizing RAM, CPU and Storage
Testen Sie Ihr Setup auf ccloud³
Registrieren Sie sich in der ccloud³ und erhalten Sie 200 € Startguthaben für Ihr Projekt – z. B. für eine PostgreSQL-VM mit automatischen Backups.