VPS Snaps

How to back up ClickHouse

ClickHouse has backups built in. Allow a destination in the server config, then run BACKUP DATABASE db TO Disk('backups', 'db.zip') (or TO S3(...)) while the server keeps running, and bring it back with RESTORE DATABASE db FROM ..., under a new name with AS if the original still exists. Add base_backup for incremental backups and ASYNC to run long ones in the background, watching them in system.backups.

9 min readUpdated Checked against official documentation

Pick a method

In October 2026 the current ClickHouse releases are 26.9 (stable) and 26.8 and 26.3 (long-term support). A self-hosted server has three ways to back up:

MethodHow it worksUse it when
BACKUP and RESTORE (SQL)The server writes table definitions and data to a local disk, a path or S3Almost always; this guide
clickhouse-backup (Altinity, open source)Freezes parts with hard links, then uploads them to object storage or SFTPYou already run it, or want its retention settings and REST API
ALTER TABLE ... FREEZEHard-links one table's parts into /var/lib/clickhouse/shadow/; copying them off is up to youOlder servers, or a one-off copy of a partition

Check the size first. Parts on disk are already compressed, so a backup comes out close to this figure:

Terminal
clickhouse-client --query "SELECT database, formatReadableSize(sum(bytes_on_disk)) AS size FROM system.parts WHERE active GROUP BY database"

If analytics shows 48.00 GiB, plan for about 48 GiB per full backup on the backup disk, and as much again for each copy you keep off the server.

Allow a backup destination

Disk() and File() refuse to write anywhere the server config doesn't list. Create a directory, ideally on a different disk from /var/lib/clickhouse, and give it to the clickhouse user:

Terminal
sudo mkdir -p /var/backups/clickhouse && sudo chown clickhouse:clickhouse /var/backups/clickhouse
/etc/clickhouse-server/config.d/backup_disk.xml
<clickhouse>
    <storage_configuration>
        <disks>
            <backups>
                <type>local</type>
                <path>/var/backups/clickhouse/</path>
            </backups>
        </disks>
    </storage_configuration>
    <backups>
        <allowed_disk>backups</allowed_disk>
        <allowed_path>/var/backups/clickhouse/</allowed_path>
    </backups>
</clickhouse>
  • <disks><backups> defines a local disk named backups; Disk('backups', ...) refers to it by that name.
  • allowed_disk lists the disks BACKUP may use. Repeat the element to allow more.
  • allowed_path does the same for File(). A relative File() path lands under the first allowed_path.
Terminal
sudo systemctl restart clickhouse-server

Back up a database

Terminal
clickhouse-client --query "BACKUP DATABASE analytics TO Disk('backups', 'analytics-2026-10-04.zip')"

If your ClickHouse user has a password, add --password (the client then asks for it) or use the client config file shown under the nightly script. The command returns an operation ID and BACKUP_CREATED when it finishes; you don't stop the server. The .zip name writes one archive. .tar, .tar.gz and .tar.zst work too, and a name with no extension writes a directory. TO File('analytics-2026-10-04.zip') does the same through allowed_path. Variations:

  • TABLE analytics.events backs up one table; PARTITIONS '2026-10-01' narrows it to partitions.
  • EXCEPT TABLES analytics.scratch leaves a table out of a database backup.
  • ALL takes every database, system included; its log tables, such as query_log, back up like any other table and can be large.
  • SETTINGS compression_method = 'lzma', compression_level = 3 sets compression; password = '...' encrypts, for .zip and .zipx names only.
In the backupNot in the backup
Definitions of the databases, tables, views and dictionaries you name, and the tables' data/etc/clickhouse-server: config.xml, users.xml, config.d
SQL-created users, roles, quotas, row policies and settings profiles, if you add their system tables (next section)Users and profiles defined in XML files
User-defined SQL functions, if you add system.functionsRows of engines that only point at outside data (Kafka, MySQL, S3): their definitions are saved, the data stays where it lives

Include users, roles and functions

Access entities created with SQL are saved only when you name their system tables. ClickHouse stores them as the SQL statements that recreate them:

Terminal
clickhouse-client --query "BACKUP DATABASE analytics, TABLE system.users, TABLE system.roles, TABLE system.settings_profiles, TABLE system.row_policies, TABLE system.quotas, TABLE system.functions TO Disk('backups', 'analytics-full-2026-10-04.zip')"

On restore, users that already exist are skipped by default. Anything defined in users.xml or config.d is a file: back up /etc/clickhouse-server with ordinary file backups, encrypted, since it holds password hashes and keys.

Back up to S3

S3() takes a URL ending in the backup's own directory, plus an access key ID and secret. For S3-compatible storage, use the provider's endpoint followed by the bucket and path. See setting up an S3 bucket for the bucket and key.

Terminal
clickhouse-client --query "BACKUP DATABASE analytics TO S3('https://my-ch-backups.s3.us-east-1.amazonaws.com/analytics/2026-10-04/', 'ACCESS_KEY_ID', 'SECRET_ACCESS_KEY')"

Keys typed into queries end up in scripts and shell history. A named collection keeps them in the server config instead:

/etc/clickhouse-server/config.d/s3_backups.xml
<clickhouse>
    <named_collections>
        <s3_backups>
            <url>https://my-ch-backups.s3.us-east-1.amazonaws.com/analytics/</url>
            <access_key_id>ACCESS_KEY_ID</access_key_id>
            <secret_access_key>SECRET_ACCESS_KEY</secret_access_key>
        </s3_backups>
    </named_collections>
</clickhouse>
Terminal
clickhouse-client --query "BACKUP DATABASE analytics TO S3(s3_backups, '2026-10-04/')"

The second argument is appended to the collection's url. Make the file readable only by root and the clickhouse group (chown root:clickhouse, chmod 640). From 26.8, ClickHouse refuses .zip names on S3; use a directory, as here, or .tar.gz.

Incremental backups

base_backup points a new backup at an earlier one; only what changed since that base is stored. A common pattern is a full backup on Sunday and a daily incremental that names Sunday's backup as its base:

Terminal
clickhouse-client --query "BACKUP DATABASE analytics TO Disk('backups', 'analytics-2026-10-05-inc.zip') SETTINGS base_backup = Disk('backups', 'analytics-2026-10-04.zip')"

To restore, name only the incremental; ClickHouse reads the unchanged files from the base. With S3() destinations, add use_same_s3_credentials_for_base_backup = 1 to reuse the keys for the base.

Every incremental needs its base, in the same place under the same name. Delete or move the base, through a retention rule or a cleanup script, and those incrementals can no longer be restored.

Run long backups in the background

A large backup can outlast your terminal session. ASYNC returns at once with status CREATING_BACKUP; SETTINGS id names the operation so you can find it:

Terminal
clickhouse-client --query "BACKUP DATABASE analytics TO Disk('backups', 'analytics-2026-10-04.zip') SETTINGS id = 'analytics-2026-10-04' ASYNC"
Terminal
clickhouse-client --query "SELECT status, error, formatReadableSize(total_size) AS size, start_time, end_time FROM system.backups WHERE id = 'analytics-2026-10-04' FORMAT Vertical"

The status moves to BACKUP_CREATED or BACKUP_FAILED, with the reason in error; restores show RESTORING, RESTORED or RESTORE_FAILED. system.backups empties when the server restarts. system.backup_log keeps the history.

Restore

Restore next to the live data first, then compare:

Terminal
clickhouse-client --query "RESTORE DATABASE analytics AS analytics_restored FROM Disk('backups', 'analytics-2026-10-04.zip')"
Terminal
clickhouse-client --query "RESTORE TABLE analytics.events AS analytics.events_restored FROM Disk('backups', 'analytics-2026-10-04.zip')"
  • By default RESTORE creates missing databases and tables. It fills an existing table only if it is empty and has the same definition; otherwise it stops with an error (see below).
  • allow_non_empty_tables = true adds the backup's rows to a table that has data, so rows can be duplicated.
  • PARTITIONS '2026-10-01' restores only those partitions.
  • structure_only = true restores the CREATE statements without data. From 26.8, restore_table_data, restore_access_entities and restore_functions switch each category on or off.
  • RESTORE isn't transactional: if it fails partway, tables it already finished stay restored.

On a new server: install ClickHouse, add the same backup_disk.xml, copy the archive into /var/backups/clickhouse, restart, and run RESTORE. Check SELECT count() on your main tables against the old figures.

Run it every night

The client reads its login from /root/.clickhouse-client/config.xml (chmod 600). Use an admin user, or a dedicated one with the BACKUP privilege on what it saves; current versions also check the destination against the user's SOURCES grants (FILE, DISK, S3).

/root/.clickhouse-client/config.xml
<config>
    <user>default</user>
    <password>long-random-password</password>
</config>

The script starts the backup in the background, polls until it ends, exits non-zero on failure and keeps two days of archives:

/usr/local/bin/clickhouse-backup.sh
#!/bin/bash
set -euo pipefail
NAME="analytics-$(date +%F-%H%M)"

clickhouse-client --query "BACKUP DATABASE analytics TO Disk('backups', '$NAME.zip') SETTINGS id = '$NAME' ASYNC" > /dev/null

while true; do
  STATUS=$(clickhouse-client --query "SELECT status FROM system.backups WHERE id = '$NAME'")
  case "$STATUS" in
    BACKUP_CREATED) break ;;
    CREATING_BACKUP) sleep 30 ;;
    *) echo "$(date -Is) $NAME failed: $STATUS" >&2
       clickhouse-client --query "SELECT error FROM system.backups WHERE id = '$NAME'" >&2
       exit 1 ;;
  esac
done

find /var/backups/clickhouse -name 'analytics-*.zip' -mtime +1 -delete
echo "$(date -Is) created $NAME.zip"
Terminal
sudo chmod 700 /usr/local/bin/clickhouse-backup.sh
/etc/cron.d/clickhouse-backup
30 2 * * * root /usr/local/bin/clickhouse-backup.sh >> /var/log/clickhouse-backup.log 2>&1

Then copy /var/backups/clickhouse off the server, to object storage or another provider (3-2-1 rule). Cron schedules covers timing and alerts. Once a month, restore the newest archive on a spare server and time it (testing restores).

Clusters and replicated tables

On a cluster, BACKUP DATABASE analytics ON CLUSTER 'main' TO S3(...) runs on every host, coordinated through ClickHouse Keeper or ZooKeeper, and RESTORE ... ON CLUSTER reverses it. All hosts write into one destination, so use shared storage such as S3, and a directory name: archives fail with Using archives with backups on clusters is disabled.

clickhouse-backup and FREEZE

clickhouse-backup is Altinity's MIT-licensed tool (2.8.1 in September 2026). It must run on the ClickHouse host, because it reads /var/lib/clickhouse, and is configured in /etc/clickhouse-backup/config.yml. clickhouse-backup create_remote <name> creates and uploads a backup, list remote shows them, and restore_remote <name> brings one back. --rbac adds users and roles, --configs the server's config files, and backups_to_keep_remote sets retention. With use_embedded_backup_restore: true it drives BACKUP and RESTORE instead of freezing.

ALTER TABLE analytics.events FREEZE WITH NAME 'nightly' hard-links the table's parts under /var/lib/clickhouse/shadow/nightly/, almost instantly. It saves data only, not the table definition, and only on the local server. To restore, copy the parts into the table's detached directory and run ALTER TABLE ... ATTACH PARTITION; ALTER TABLE analytics.events UNFREEZE WITH NAME 'nightly' removes the frozen copy.

Never chmod or chown files under shadow/ or /var/lib/clickhouse/backup/. Hard links share permissions with the live data files, so the change hits the running tables too.

Common errors

ErrorFix
The 'backups.allowed_disk' configuration parameter is not set, cannot use 'Disk' backup engineAdd backup_disk.xml and restart the server.
... is not allowed for backups, see the 'backups.allowed_disk' configuration parameterThe name in Disk() isn't listed. Check the spelling or add another allowed_disk element.
The 'backups.allowed_path' configuration parameter is not set, cannot use 'File' backup engineAdd allowed_path, or use Disk().
Backup Disk('backups', 'analytics-2026-10-04.zip') already exists. (BACKUP_ALREADY_EXISTS)Backups are never overwritten. Use a new name; the script adds the time.
Backup ... not found (BACKUP_NOT_FOUND)Wrong disk or name, or an incremental's base was deleted or moved.
Cannot restore the table <db>.<table> because it already contains some data.Restore AS a new name, drop the table first, or set allow_non_empty_tables = true.
The table has a different definition: ... comparing to its definition in the backup: ...The schema changed since the backup. Restore AS a new name, or set allow_different_table_def = 1.
Zip archive format is not supported for S3 backups ...26.8 and newer: back up to S3 as a directory or .tar.gz.
Password is not applicable, backup cannot be encryptedpassword works only with .zip or .zipx names.

Frequently asked questions

Does ClickHouse BACKUP need downtime?
No. It runs on the live server. Add ASYNC for long backups so the client doesn't have to stay connected, and follow progress in system.backups.
Are ClickHouse backups incremental?
Only when you ask. Add SETTINGS base_backup = Disk(...) or S3(...) and the new backup stores only what changed since that base, which you must keep.
Does a ClickHouse backup include users and passwords?
Only users and roles created with SQL, and only if you include system.users and the related system tables. Users defined in users.xml are config files; back them up as files.
Should I use clickhouse-backup or the BACKUP command?
BACKUP is built in and needs nothing else. Altinity's clickhouse-backup adds remote retention, a REST API and config-file backups, and can drive BACKUP itself.
Can I restore a ClickHouse backup under a different name?
Yes: RESTORE DATABASE db AS db_restored, or RESTORE TABLE db.t AS db.t_restored, from the same backup.

How this was checked

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