Skip to main content

Backup and restore

MonoPbx backs up Postgres (one consistent snapshot), ClickHouse (call records, quality, events, audit and application logs), the install secrets including the master key, the non-secret configuration and, if you ask for it, recordings and voicemail. Everything is encrypted with a passphrase you choose and written to a local directory, an S3-compatible service or Google Cloud Storage. A restore onto a fresh host needs two things: the backup and its passphrase.

:::warning Keep the passphrase MonoPbx cannot recover it. Keep a copy off the server. Without it a backup is useless. :::

What is in a backup​

PartContent
postgresEvery table: companies, users, extensions, trunks, settings, sealed secrets, certificates, and the backup settings themselves.
clickhouseCall records, legs, call quality, call events, the audit log and application logs. Optional.
secretsThe master key and its id, the JWT signing key pair, the relay key pair and the provisioning secret. Database passwords and the setup token are not included; they belong to the host.
configThe non-secret environment and any config files you name. A restore extracts it for you to review but does not apply it.
objectsRecordings and voicemail. Optional, and a full copy every time. For large sites use bucket versioning or replication instead.

Not included on purpose: sessions and refresh tokens (a restore signs everyone out), event outboxes, and the backup run history. Valkey is not backed up; it holds live state that rebuilds on start.

A backup is a directory backups/<id>/ on the target. A set without manifest.json is incomplete and is never listed or restored.

Security​

The passphrase (at least 12 characters) is turned into a key with Argon2id. Each part is encrypted with AES-256-GCM in authenticated chunks, so any change, truncation or reordering fails to open. The manifest lists the size and SHA-256 of each part, so integrity can be checked without the passphrase. The passphrase is never read from a command line; use a 0600 file, standard input, the environment or a prompt.

Taking a backup​

Docker Compose​

The bundle has a one-off service mpbx-ops (profile ops, never started by up):

cd deploy/compose/prod
umask 077; openssl rand -base64 24 > backup.pass # keep a copy OFF this host
docker compose run --rm --user "$(id -u):$(id -g)" -v "$PWD/backup.pass:/run/backup.pass:ro" \
mpbx-ops backup --passphrase-file /run/backup.pass --prune

The default target is ./backups (MPBX_BACKUP_DIR in .env). Copy it somewhere that survives the host. On a VM install, run Compose through sudo mpbx-ctl compose ....

On a schedule​

mpbx run includes a scheduler. Set the schedule and target once with the API and every node checks the clock; one of them runs each slot. No cron job is needed.

PUT /api/v1/system/backups/settings
{"enabled": true, "schedule": "daily", "run_hour": 2,
"retention": {"keep_last": 7, "keep_daily": 7, "keep_weekly": 4, "keep_monthly": 6},
"target": {"backend": "s3", "bucket": "pbx-backups", "prefix": "mpbx",
"region": "eu-central-1", "access_key_id": "...", "secret_access_key": "..."},
"passphrase": "..."}

POST /api/v1/system/backups/settings/check-target writes, reads back and deletes a test file
POST /api/v1/system/backups back up now (202 and a run to poll)
GET /api/v1/system/backups the sets on the target
GET /api/v1/system/backups/runs/{run_id} poll a run
POST /api/v1/system/backups/{id}/verify
DELETE /api/v1/system/backups/{id}

These routes need system.settings:read or system.settings:write, held by System Owner and System Admin. The target and passphrase are write-only and stored sealed. A failed scheduled backup is retried after 15 minutes, three attempts per slot.

Target examples: {"backend":"local","root":"/var/backups/mpbx"}, {"backend":"s3",...} (add endpoint and path_style for MinIO or other S3-compatible services) and {"backend":"gcs","bucket":"...","service_account_path":"..."}. Do not use the bucket that holds your recordings if you can avoid it: a backup next to the data it protects does not survive losing that bucket.

Verifying​

mpbx backup verify latest --target ... # sizes and SHA-256 of every part
mpbx backup verify latest --target ... --passphrase-file ... # also decrypts every chunk
mpbx restore latest --target ... --passphrase-file ... --dry-run # a restore rehearsal

Run the full check after changing the passphrase, the target or the version.

Restoring onto a new host​

  1. Install as usual and run mpbx init. This makes new secrets and the databases. Do not create an administrator or start mpbx yet.
  2. Start only the databases: docker compose up -d --wait postgres valkey clickhouse.
  3. Make the backup readable on the host and run:
docker compose run --rm --user "$(id -u):$(id -g)" -v "$PWD:/work" -v "$PWD/backup.pass:/run/backup.pass:ro" \
mpbx-ops restore latest --passphrase-file /run/backup.pass --sink env-file:/work/.env \
--extract-config /work/restored-config
docker compose up -d --wait

The restore stops at the first problem. It checks the passphrase first (a wrong one changes nothing), verifies every part, requires that this version knows exactly the migrations the backup has (MPBX-BACKUP-SCHEMA-MISMATCH: restore with the version that made the backup, then upgrade), refuses a database that already has users unless --force, replaces Postgres in one transaction, then ClickHouse, and writes the app secrets last.

After the restore everyone signs in again. The recovery key of the restored master key is the one saved when the original install was created, not the one the new host printed. Delete any MPBX_SETUP_TOKEN_* lines mpbx init left in .env.

Restoring an earlier state of a running install is also possible with POST /api/v1/system/backups/{id}/restore (with the backup's own passphrase and the backup id as confirmation), but only for a backup with the same master key. Restart the PBX when the run ends.

Limits​

  • ClickHouse tables are consistent individually, not across tables.
  • Object backups are full copies; there is no incremental mode yet.
  • A backup restores only onto the same schema version.
  • One backup or restore runs at a time per install.
  • Periodic automatic test restores are planned, not built.