Skip to content

Backups and recovery

The worker backs up the database and media originals every night, keeps a sensible history, and tests the latest backup every week by restoring it into a scratch database. You don’t have to remember to do anything, but you should know where backups go and keep the encryption key safe.

Most mistakes don’t need a restore. The admin keeps history for you:

Problem Fix
A bad edit was published Open the item, go to its history, pick the good revision, Restore, then publish again. See Revisions.
Something was deleted Deleted content goes to the Trash first, where it can be restored.
A page’s address changed and old links break A redirect is created automatically. Check Content → Redirects.
The design looks wrong after a change Design tokens and templates have their own history with restore.
A theme switch went badly Switch back. Each theme keeps its own customizations.
A plugin breaks pages Deactivate it under Extensions. Its data is kept.
  • The whole database, with pg_dump: content and every revision, people, settings, design, plugin data.
  • Media originals (the files people uploaded). Resized image versions aren’t backed up; the worker recreates them.

Backups go to the media storage by default. It’s much safer to send them somewhere else: a bucket at another provider or account (BACKUP_STORAGE_DRIVER=s3), or a separately mounted disk (local).

Set BACKUP_ENCRYPTION_KEY to encrypt backups. A backup can’t be restored without the key, so keep a copy away from the server.

Setting Default Meaning
BACKUP_CRON 30 2 * * * Back up every night at 02:30.
BACKUP_KEEP 14 Keep the newest 14 backups…
BACKUP_KEEP_WEEKLY 8 …plus one per week for 8 weeks…
BACKUP_KEEP_MONTHLY 12 …plus one per month for a year.
BACKUP_VERIFY_CRON 15 4 * * 0 Restore drill every Sunday at 04:15.

A failed backup or drill shows on the admin dashboard.

Run these on the server, with the same environment as the site (in Docker: docker compose -f docker/compose.yml run --rm migrate node packages/backup/src/cli/main.ts …).

Terminal window
pnpm backup # back up now
pnpm backup list # list backups
pnpm backup verify latest # restore drill into a scratch database; the live site is untouched
pnpm backup restore latest --yes # replace the database with a backup (stop web and worker first)
  1. Stop web and worker. If the database still works, take a fresh backup first: everything after the backup you restore will be lost.
  2. Pick a backup with pnpm backup list.
  3. Run pnpm backup restore <id> --yes. It checks the backup’s checksum, replaces the database in one step, and puts back any missing media files.
  4. Run pnpm db:migrate, then start worker and web, sign in, and check a few pages.

The full step-by-step procedures, including rebuilding a lost server, rotating the encryption key, and moving hosts with a site export, are in the recovery guide.

Settings → Download JSON export saves all content, revision history, menus, patterns, design, plugin data, and media addresses in one file. On the new install, run setup, then use Settings → Import a site. The worker copies the media files across from the old site, so keep it running until the import finishes. People, sessions, and API tokens are not included: invite people again, and re-create webhooks.