VPS Snaps

How to back up and restore Nextcloud

Nextcloud's admin manual backs up five things: the config folder, the data folder, the custom apps and theme folders, and the database. Turn on maintenance mode with occ maintenance:mode --on, copy the folders with rsync, dump the database with --single-transaction, then turn it off. config/config.php holds the secret and instanceid that encrypted data and sessions depend on, so a data backup without it is incomplete.

9 min readUpdated Checked against official documentation

What to back up

PartWhereHolds
Configconfig/ in the install directoryconfig.php: database password, secret, passwordsalt, instanceid.
Datadatadirectory in config.php, data/ by defaultUsers' files, versions, deleted files, the hidden .ncdata file, and encryption keys.
Apps you installedThe folder apps_paths in config.php namesNeeded only if you added apps.
Themesthemes/Custom themes.
DatabaseMySQL/MariaDB, PostgreSQL or SQLiteUsers, shares, file metadata, calendars and contacts.

The simplest way to cover all of it is the manual's alternative: copy the whole installation directory, which also brings back the exact Nextcloud version, plus the data directory if it lives elsewhere. Run occ as the web server user, www-data on Debian and Ubuntu, as the manual does. To find the data directory:

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ config:system:get datadirectory

dbtype, dbname, dbhost and dbuser work the same way. Keep the backup's paths: Nextcloud identifies local storage by its absolute path, and its docs say moving the data directory is not supported without database edits.

The manual says secret and instanceid are tied to all encrypted data and user sessions and must be copied verbatim. With server-side encryption on, the keys live in data/files_encryption and data/<user>/files_encryption, and losing them or the secret means permanent data loss. Keep config.php with every data backup, and keep both private.

Turn on maintenance mode

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ maintenance:mode --on

This locks the sessions of logged-in users, blocks new logins and shows a maintenance page, so files and database cannot drift apart while you copy them. Setting 'maintenance' => true in config.php does the same.

Copy the folders

The manual's command, here writing to a backup disk mounted at /mnt/backup:

Terminal
rsync -Aavx /var/www/nextcloud/ /mnt/backup/nextcloud-dirbkp_$(date +%Y%m%d)/
  • -a copies recursively and keeps permissions, owners, timestamps and symlinks. Timestamps matter: the manual warns that clients re-download every file if they change.
  • -A keeps ACLs. -v lists each file.
  • -x stays on one filesystem. If data is a separate disk mounted inside the install directory, -x skips its contents: copy it with its own rsync.
  • The trailing slash on the source copies its contents rather than the directory itself.

Maintenance mode lasts as long as the copy. To shorten it, add --delete to the rsync, run it once while Nextcloud is online, then turn maintenance mode on and run it again: the second pass only transfers what changed. Only that second pass is consistent.

Each run makes a full copy. The script below keeps daily history with hard links instead, and restic or Borg add deduplication and encryption.

Dump the database

Do this while maintenance mode is still on. For MySQL or MariaDB, put the credentials from config.php in an option file so the password stays out of ps:

/etc/mysql/nextcloud-backup.cnf
[client]
user=nextcloud
password="your-password-here"
host=localhost
Terminal
chmod 600 /etc/mysql/nextcloud-backup.cnf
Terminal
mysqldump --defaults-extra-file=/etc/mysql/nextcloud-backup.cnf --single-transaction --default-character-set=utf8mb4 --no-tablespaces nextcloud | gzip > /mnt/backup/nextcloud-sqlbkp_$(date +%Y%m%d).sql.gz
  • --single-transaction dumps InnoDB tables from one snapshot without locking them.
  • --default-character-set=utf8mb4 is the manual's addition when 4-byte support is on, which 'mysql.utf8mb4' => true in config.php shows.
  • --no-tablespaces avoids needing the global PROCESS privilege, which a Nextcloud user granted only its own database lacks.
  • On MariaDB, mariadb-dump takes the same options, as the manual shows.

PostgreSQL, with the password in ~/.pgpass and -w so a missing password fails instead of prompting:

Terminal
pg_dump -h localhost -U nextcloud -w -f /mnt/backup/nextcloud-sqlbkp_$(date +%Y%m%d).sql nextcloud

SQLite keeps the database as a file in the data directory, so the copy above includes it; the manual also dumps it with sqlite3 data/owncloud.db .dump. See the SQLite guide and the mysqldump guide.

Then turn maintenance mode off:

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ maintenance:mode --off

Restore on the same or a new server

The manual is firm: you need the config, the data and the database; you cannot restore without all three. On a new server, first install the web server, a supported PHP with Nextcloud's extensions, the database server, and every service config.php uses, such as Redis. Then copy the files back:

Terminal
rsync -Aax /mnt/backup/nextcloud-dirbkp_20261003/ /var/www/nextcloud/

The database must be empty before the import, so the manual drops and recreates it. As root at the MySQL or MariaDB prompt, with the character set from Nextcloud's database setup guide:

MySQL prompt
DROP DATABASE IF EXISTS nextcloud;
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

On a new server, also create the user with the password in config.php, and put the option file from above in place:

MySQL prompt
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'your-password-here';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
Terminal
gunzip -c /mnt/backup/nextcloud-sqlbkp_20261003.sql.gz | mysql --defaults-extra-file=/etc/mysql/nextcloud-backup.cnf nextcloud

For PostgreSQL, drop and recreate the database the same way, then load the dump with psql -h localhost -U nextcloud -d nextcloud -f and the file. On a new server, review config.php before starting: the manual lists dbhost, dbname, dbuser, dbpassword, trusted_domains, overwrite.cli.url, the memcache and Redis settings, mail_smtphost and logfile. Keep datadirectory, secret, instanceid and serverid exactly as they were. Then fix ownership as the install guide does, and restore the background jobs cron entry for www-data:

Terminal
chown -R www-data:www-data /var/www/nextcloud/
crontab -u www-data -e
*/5 * * * * php -f /var/www/nextcloud/cron.php

A backup taken in maintenance mode restores in maintenance mode, because the flag lives in config.php. Start the web server, check you see the maintenance notice and a clean log, then run occ maintenance:mode --off as above.

Resync clients and rescan files

If the restored data is older than what users' desktop clients hold, run this once:

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ maintenance:data-fingerprint

Clients then know a backup was restored: instead of making their copies match the server, they upload files the server lacks and ask users when contents differ. The manual warns it can cause conflict dialogs, so use it only when the backup is outdated, or after a move when the old config.php had a non-empty data-fingerprint. It does not need maintenance mode.

If files and database come from different moments, or files were copied into the data directory by hand, update the file cache:

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ files:scan --all

files:scan belongs to the files app, and apps do not load in maintenance mode, so run it after maintenance mode is off.

Verify the restore

Terminal
sudo -E -u www-data php /var/www/nextcloud/occ status

Expect installed: true, the version you backed up, maintenance: false and needsDbUpgrade: false. For scripts, occ status -e prints nothing and exits 0 when all is well and 1 in maintenance mode. occ integrity:check-core checks core files against Nextcloud's signature. Then log in as a test user, open a few files and a share link, check Calendar and Contacts if you use them, and watch a desktop client sync. Testing restores explains how to rehearse this.

Automate it with cron

This root script follows the manual's order, turns maintenance mode off even if a step fails, and keeps one dated folder per day. --link-dest makes files unchanged since the previous run hard links, so each folder looks complete but only changes take space. If the data directory is outside the install directory, add a second rsync for it.

/usr/local/bin/nextcloud-backup.sh
#!/bin/bash
set -euo pipefail

NC="/var/www/nextcloud"
DEST="/mnt/backup/nextcloud"
KEEP_DAYS=14
TODAY="$DEST/$(date +%F)"
occ() { sudo -u www-data php "$NC/occ" "$@"; }

mkdir -p "$TODAY"
occ maintenance:mode --on
trap 'occ maintenance:mode --off' EXIT

mysqldump --defaults-extra-file=/etc/mysql/nextcloud-backup.cnf \
  --single-transaction --default-character-set=utf8mb4 --no-tablespaces \
  nextcloud | gzip > "$TODAY/nextcloud-db.sql.gz"
gunzip -c "$TODAY/nextcloud-db.sql.gz" | tail -n 1 | grep -q "Dump completed"

rsync -Aax --delete --link-dest="$DEST/latest/nextcloud" "$NC/" "$TODAY/nextcloud/"

occ maintenance:mode --off
trap - EXIT
ln -sfn "$TODAY" "$DEST/latest"

CUTOFF="$(date -d "-$KEEP_DAYS days" +%F)"
for d in "$DEST"/20??-??-??; do
  if [[ "$(basename "$d")" < "$CUTOFF" ]]; then rm -rf -- "$d"; fi
done
Terminal
chmod 700 /usr/local/bin/nextcloud-backup.sh
/etc/cron.d/nextcloud-backup
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

30 2 * * * root flock -n /run/lock/nextcloud-backup.lock /usr/local/bin/nextcloud-backup.sh >> /var/log/nextcloud-backup.log 2>&1

The first run warns that latest does not exist yet and makes a full copy. Keep /mnt/backup on a different disk from the server's own, then send it off-site with rclone or restic; the cron guide covers alerts.

Nextcloud All-in-One

Nextcloud AIO, the Docker-based install, has its own backup built on BorgBackup and run from the AIO interface: incremental, compressed and encrypted, to a local path or a remote Borg repository, with restores from the same screen. Its default retention is --keep-within=7d --keep-weekly=4 --keep-monthly=6. On AIO, use it instead of the steps above.

Common errors

ErrorFix
Access through untrusted domainAdd the hostname to trusted_domains in config.php.
Your data directory is invalid..ncdata is missing from the data directory: the copy skipped hidden files, or datadirectory points elsewhere. Copy with rsync -a, not cp data/*.
The maintenance page never goes awayThe restored config.php has 'maintenance' => true. Run occ maintenance:mode --off.
Nextcloud is in maintenance mode, no apps are loaded.App commands such as files:scan need maintenance mode off.
Files are listed but cannot be opened, or are missingThe file cache and the data differ. Run occ files:scan --all.
Desktop clients remove files the restored server lacksRun occ maintenance:data-fingerprint, ideally before clients reconnect, so they upload them instead.
Redis server went awayInstall and configure Redis on the new server, as config.php expects.
Access denied; you need (at least one of) the PROCESS privilege(s) for this operationAdd --no-tablespaces to mysqldump.

Frequently asked questions

Do I need maintenance mode to back up Nextcloud?
The manual's procedure uses it, so files and database match. Without it, a file uploaded mid-copy can exist in one and not the other; files:scan can repair the file cache afterwards, but shares and metadata may not match.
Is backing up the data folder enough?
No. The manual says a restore needs the config, the data and the database. config.php holds the secret that encrypted data depends on, and the database holds shares, calendars and contacts.
When should I run occ maintenance:data-fingerprint?
After restoring a backup that is older than the files on users' devices. Clients then upload what the server lost instead of deleting it locally.
How do I back up a very large Nextcloud data directory?
Use an incremental tool: rsync with --link-dest, restic or Borg. After the first run each backup copies only changes, which keeps the maintenance window short.

How this was checked

Commands, limits and prices were checked against these official pages, on October 3, 2026: