Backups
A backup counts only once it has been verified. A copy leaves the server encrypted. And rotating the key never strands the backups the old key wrote.
Local backups
klyrn backup create example.com
klyrn backup list example.com
klyrn backup schedule example.com daily --keep 7
klyrn backup schedule example.com none
klyrn backup delete 42
A backup contains the site's files, its databases and its cron entries, with a
manifest.json carrying a SHA-256 for every file. Caches, logs, the temp
directory and node_modules are excluded: they are rebuildable, and they are
most of the size.
It is only marked verified after the archive is listed back and every checksum in the manifest matches. A backup that was written but never read is a hope, not a backup.
Retention keeps the last 7 by default, or whatever --keep says, between 1 and
60. The scheduled pass runs a site at most once every 23 hours. Archives live under
/var/lib/klyrn/backups/, readable by root only.
Restoring
klyrn backup restore 42
klyrn backup restore 42 --yes # skip the confirmation prompt
A restore verifies every checksum first, unpacks into a staging directory, and only then swaps into place. It can recreate a site that was deleted, including its database and its user with a fresh password written into the site's configuration.
In the panel it is a six-step wizard ending in type-to-confirm, because a restore overwrites a working site as readily as a broken one. A restore is also checked against the account's disk limit, using the backup's own size.
Off-site copies
klyrn backup remote # destination and its state
klyrn backup remote list example.com # what is in the bucket
klyrn backup remote fetch klyrn/example.com/20260906-090000
klyrn backup remote delete klyrn/example.com/20260906-090000
The destination is any S3-compatible bucket you control. Copies are encrypted on the server with XChaCha20-Poly1305 before they leave it, so the bucket operator never holds plaintext, and uploads above 1 GB are multipart.
One object in the destination is not encrypted: a small
keyinfo.json holding a key fingerprint and a couple of counts, never a key.
It exists so a rebuilt server can say "import key 3f1a9c04b7d25e88" instead of
failing with "wrong key" and leaving you to guess which one.
Keys and rotation
klyrn backup key list # version, fingerprint, created, which one writes
klyrn backup key export # the current key, in full
klyrn backup key export <fingerprint>
klyrn backup key rotate --note "annual rotation"
klyrn backup key import klyrn-bk1-… --make-current
Rotating adds a key. It never removes one. Any design where rotating the key strands last month's backups is not key rotation, it is data loss with a progress bar. A backup that could be restored yesterday must still be restorable today.
A fingerprint is the first eight bytes of SHA-256 of the key, hex encoded: it identifies a key without being usable as one. A restore picks the key by the fingerprint the backup recorded, and for backups written before fingerprints existed it tries each key in turn, newest first. The archive format authenticates its first chunk, so a wrong key is rejected immediately rather than producing plausible rubbish.
Importing a key to read another server's backups does not change what new
backups are encrypted with: the imported key goes second from the end unless you pass
--make-current. Importing a key this server already holds is refused by
fingerprint.
What a backup is not
Stated so you plan around it rather than discover it:
- Backups are per site, not per server. The server-level answer is the recovery manifest.
- You cannot back up only the files or only the database. A backup is the whole site.
- One S3-compatible destination at a time. Verified against MinIO.
- Nothing is deleted from the bucket when a local backup is pruned. Removing a remote copy is an explicit command.