Skip to content

Backup and restore

LevelScopeTool
Infrastructureentire clusteroperator’s database snapshots
Tenantone organisationnightly export (see below)
Zoneone domaintime-travel restore (EE)

No level replaces another: a cluster snapshot does not help when a single tenant has broken something for themselves, and a zone restore does not help with a database outage.

With DNSE_BACKUP_DIR set, a nightly job writes a complete logical export per organisation (JSON Lines, including history). Writes are atomic, so an aborted run never appears as a restore point.

Terminal window
DNSE_BACKUP_DIR=/var/lib/dnseditor/backups

Retention is rolling: points older than 35 days are removed on the next run.

The export decrypts the database fields, because it has to be restorable. That would leave provider credentials, private certificate keys and TOTP secrets in the clear next to the database, protected by file permissions alone. Since round 105 the stream is therefore sealed as it is written: AES-256-GCM, the same key as the fields (including rotation via the key id), in 64 KiB frames.

You can tell by the name: new points are called …jsonl.enc, older ones …jsonl. Both remain valid restore points, the change devalues no existing one. A download delivers the decrypted stream (the encryption protects the file on the server, not against the administrator the data belongs to).

Every frame carries its sequence number and the last one an end marker. A truncated or altered file is therefore rejected instead of restoring half a tenant.

Points from before the change are sealed by a one-off run:

Terminal window
systemd-run --unit=dnse-backup-seal --wait \
--property=EnvironmentFile=/etc/dnseditor-cloud/env \
--property=User=dnseditor-cloud \
/usr/bin/dnseditor-cloud backup-seal

Whether the stock is readable is answered by the counter-check, which opens every point and reads it in full:

Terminal window
systemd-run --unit=dnse-backup-verify --wait \
--property=EnvironmentFile=/etc/dnseditor-cloud/env \
--property=User=dnseditor-cloud \
/usr/bin/dnseditor-cloud backup-verify

The command needs neither database nor a running service, skips points that are already sealed and can be repeated. What it cannot do is overwrite the old files securely: on a journalling filesystem the content may survive in free blocks. Rule that out with full-disk encryption.

In production the directory points to the object storage of the respective home region — an EU backup thus stays in the EU.

Visible in two places:

  • Tenant admin — Administration → Backup. The self-service surface is a product function of the plan feature org-backup (default: Enterprise). Every organisation is backed up regardless — the feature only controls who sees the points themselves and may request a restore.
  • Operator — in the tenant detail, for every organisation and plan, additionally with Export now and a download per point.

A restore deliberately passes through several hurdles:

  1. Type the name. The organisation’s name must be entered to confirm.
  2. Safety export. Immediately beforehand, the current state is exported — making the restore itself reversible.
  3. Log. Every request is recorded with timestamp, initiator and target point.
  4. Cooldown. No second request is accepted within 15 minutes.

After the guardrails, the restore executes (tenants.Import, since round 83): the organisation is archived (writes are rejected), the selected point replaces the data completely, then it is active again. In detail:

  • Secrets and personal data are re-encrypted with the current key — since v28 this includes e-mail addresses, display names and the billing master data including invoice PDFs; the login indexes (blind index over e-mail or username) are recomputed on import.
  • Derived views are reprojected from the imported events — only that guarantees a consistent result.
  • Geo: the organisation’s currently declared home region applies — after a move, the restore lands in the new region, not where the export once lived.
  • Abort: the organisation stays recognisably incomplete (writes fail with a clear message) instead of silently half-filled; a repeated request cleans up and starts over — the cooldown steps aside for that repair case.
  • A foreign schema state is rejected with a reason, never imported silently.

An export cannot be restored without the associated DNSE_ENCRYPTION_KEY. Until round 105 this sentence was already here but was not true: the exports carried the payload in the clear. Now it holds literally.

Both consequences follow:

  • Back the key up separately — not in the same directory, not in the same backup.
  • Back it up at all. Losing it loses the database and every backup with it. That is not a new risk but an honest one: a database without its key was already unreadable.