HitKeep Backup and Restore Guide
HitKeep keeps its operational story simple, but every 2.13 installation has an explicit storage boundary:
HITKEEP_DB_PATHis the SQLite control-plane database- every tenant, including the default tenant, stores analytics in its own DuckDB file under one
data-path
That means your backup strategy must follow the real storage boundary, not an older “copy one hitkeep.db file” assumption.
The Backup Boundary
Section titled “The Backup Boundary”Single-tenant or default-tenant only
Section titled “Single-tenant or default-tenant only”If you only use the default tenant, your live data is:
- the control-plane database at
HITKEEP_DB_PATH(commonly{data-path}/hitkeep.db) - the default tenant analytics database at
{data-path}/tenants/{default_team_id}/hitkeep.db - your archive directory if retention archiving is enabled
Multiteam installs
Section titled “Multiteam installs”Once you use non-default teams, the live data footprint becomes:
- the control-plane database at
HITKEEP_DB_PATH - one analytics database per tenant at
{data-path}/tenants/{team_id}/hitkeep.db, including the default tenant - your archive directory if retention archiving is enabled
That is why the safe rule is:
Back up the whole
data-pathtree, not onlyhitkeep.db, even when you use only the default tenant.
Built-in Backups
Section titled “Built-in Backups”HitKeep creates a compact, consistent SQLite control snapshot with VACUUM INTO and exports each tenant catalog with DuckDB.
export HITKEEP_DATA_PATH=/var/lib/hitkeep/dataexport HITKEEP_BACKUP_PATH=/var/lib/hitkeep/backupsexport HITKEEP_BACKUP_INTERVAL=60export HITKEEP_BACKUP_RETENTION=24
./hitkeepBuilt-in backups include:
shared/{timestamp}/control.db.zst, a versioned SQLite manifest, and_COMPLETEwritten last- each tenant snapshot, including the default tenant, under
tenants/{team_id}/{timestamp}/
Use this when you want consistent application-level snapshots without relying on filesystem-level tooling.
HitKeep passively checkpoints SQLite before the control snapshot and runs a required catalog-scoped DuckDB checkpoint before each tenant export. A failed checkpoint or validation fails the backup instead of publishing a completion marker.
One timestamp is successful only when the control database and every active tenant database export successfully; _COMPLETE is the cycle-wide commit marker. A tenant failure marks the whole run as failed even if other tenant exports completed. Failed or interrupted runs can leave uncommitted tenant export directories or S3 objects, but restore ignores local control snapshots without _COMPLETE and rejects an explicitly selected incomplete timestamp. Expire abandoned S3 prefixes through your normal object-storage lifecycle policy.
Instance operators can inspect the configured backup path, interval, retention, next backup, last backup, and recent failures from Administration → System Status → Operations.
Restore Semantics
Section titled “Restore Semantics”Restore is an offline operation:
./hitkeep recover restore-backup \ -from /var/lib/hitkeep/backups \ -snapshot 2026-03-08T120000Z \ -db /var/lib/hitkeep/data/hitkeep.db \ -data-path /var/lib/hitkeep/data \ -yesFor S3-backed snapshots:
./hitkeep recover restore-backup \ -from s3://my-bucket/hitkeep/backups \ -snapshot 2026-03-08T120000Z \ -yesThe restore flow is WAL-safe and engine-aware:
- Existing database files are moved aside as
.pre-restore.{timestamp}safety copies. - A SQLite control snapshot is low-memory decompressed into a temporary file, checked against its size and SHA-256 manifest, and validated with
quick_checkplusforeign_key_check. - Each tenant snapshot is imported into a temporary DuckDB file, checkpointed, and closed.
- HitKeep refuses incomplete snapshots and tenant restores that still depend on a
.wal. - Each validated file is renamed into place individually while the previous file remains as a
.pre-restore.{timestamp}safety copy. If any catalog fails, keep HitKeep stopped, correct the backup set, and rerun restore before startup. Legacy DuckDB shared snapshots are still accepted and are converted offline on the next 2.13 startup.
That means hitkeep recover restore-backup itself should not leave your restored database dependent on replaying a leftover WAL.
The .pre-restore.{timestamp} database and WAL safety copies are retained after a successful restore. Validate the restored instance first, then remove those copies according to your storage and data-retention policy; HitKeep does not rotate them automatically.
Automatic-recovery bundles under HITKEEP_DB_RECOVERY_PATH are different from backup snapshots. They preserve the exact pre-repair database and WAL for rollback or forensic work, are retained locally, and are not rotated by the backup retention setting.
Local vs External Backup Tooling
Section titled “Local vs External Backup Tooling”Built-in backups are the easiest supported option, but external tooling is still valid.
Recommended external strategy
Section titled “Recommended external strategy”Stop HitKeep before copying live DuckDB files, or use storage-level snapshots that quiesce the volume consistently. Do not copy an active database and WAL as unrelated files.
# HitKeep is stopped for this copy.# Copy the full live data treersync -az /var/lib/hitkeep/data/ backup-host:/backups/hitkeep/data/
# Copy retention archives if you use themrsync -az /var/lib/hitkeep/archive/ backup-host:/backups/hitkeep/archive/Or with object storage:
# HitKeep is stopped for this copy.rclone sync /var/lib/hitkeep/data/ remote:my-bucket/hitkeep/data/rclone sync /var/lib/hitkeep/archive/ remote:my-bucket/hitkeep/archive/For every 2.13 installation, do not back up only:
cp /var/lib/hitkeep/data/hitkeep.db /backups/That copies only the shared control plane and misses tenant-local analytics databases.
2.13 Migration and Downgrades
Section titled “2.13 Migration and Downgrades”The first 2.13 startup migrates default-tenant analytics out of hitkeep.db, drains retained non-default compatibility rows into their tenant catalogs, rewrites the control file, and converts it to SQLite. It may require one clean restart if automatic DuckDB recovery ran first. The former compact DuckDB control file remains as <db-path>.pre-sqlite-2.13.0 for migration recovery evidence; it is not a 2.12 downgrade database. Downgrading requires restoring the complete pre-upgrade control-and-tenant backup.
After a Restore
Section titled “After a Restore”After recover restore-backup finishes:
- Start HitKeep normally.
- Confirm login works.
- Open the dashboard for a site in the default tenant.
- Open at least one site from a non-default team if you use teams.
- Check that goals, funnels, ecommerce, and team-specific analytics still render.
The next normal startup may create a new, valid DuckDB .wal during runtime. That is expected. The thing to avoid is a restore that only works if an old or partial WAL is replayed.
Recovering Without a Backup
Section titled “Recovering Without a Backup”If the default tenant’s analytics database at {data-path}/tenants/{default_tenant_id}/hitkeep.db is lost and no backup exists — for example because the data path was not on persistent storage — HitKeep refuses to start and names the missing file. Two offline commands get you running again. Stop HitKeep before using either one.
Rebuild the missing database as an empty, schema-migrated file:
./hitkeep recover rebuild-default-tenantThe command requires explicit confirmation because analytics history is not restored. It refuses to overwrite a database that still opens, and it sets an unreadable leftover file aside instead of deleting it.
If retention archiving was active, your aged-out analytics still exist as Parquet exports under the archive path. Import them back after the rebuild:
./hitkeep recover import-archivesThe import is idempotent: rows that already exist are kept once, and rows that reference a deleted site, goal, or funnel are skipped rather than imported inconsistently. Data newer than your last archive run cannot be recovered this way — only a real backup holds it.
Both commands read HITKEEP_DB_PATH, HITKEEP_DATA_PATH, and HITKEEP_ARCHIVE_PATH from the environment and accept explicit -db, -data-path, and -archive-path flags.