Backup & Restore
Chatto provides built-in CLI commands for backing up and restoring all server data. Backups capture the full state of your NATS JetStream storage — users, rooms, messages, memberships, assets, and more — into a single .tar.gz archive. Backups can optionally be encrypted with a passphrase using age encryption.
For small self-hosted servers, the recommended backup is a single encrypted archive that includes encryption keys:
chatto backup -c chatto.toml --encrypt --include-keysThis is the easiest backup to restore correctly. Keep the passphrase somewhere safe; anyone with the archive and passphrase can read the restored data.
What’s included
Section titled “What’s included”Backups contain all persistent JetStream streams, KV buckets, and object stores:
- Server data — users, memberships, roles, permissions, notifications, rooms, messages, threads, reactions, message bodies
- Call facts — room-scoped call start/join/leave/end events used to rebuild active call participant state
- Assets — user avatars, server icon and banner, message attachments
What’s excluded
Section titled “What’s excluded”Certain streams are intentionally excluded:
| Stream | Reason |
|---|---|
| Encryption keys | Security: excluded by default, unless you pass --include-keys. |
| User presence | Ephemeral (memory-only), reconstructed at runtime |
| LiveKit E2EE call keys | Stored in ENCRYPTION_KEYS and excluded unless key backups are explicitly included; active calls cannot be recovered without them |
| Link preview cache | Regeneratable on demand |
| Asset cache | Regeneratable (resized image variants) |
Creating a backup
Section titled “Creating a backup”Run chatto backup while your Chatto server is running. The command connects to NATS via the client configuration in chatto.toml.
If you use embedded NATS from a fresh chatto init config, the NATS server runs in-process only and does not open a TCP listener. Uncomment nats.embedded.port before running CLI backup or key-export commands:
[nats.embedded]port = 4222bind_address = "127.0.0.1"When the embedded TCP listener is enabled, Chatto derives matching nats.client settings automatically from nats.embedded.auth_token. Keep the listener bound to localhost unless you intentionally expose NATS on a trusted private network.
# Default: saves to backups/<timestamp>.tar.gzchatto backup -c chatto.toml
# Custom output pathchatto backup -c chatto.toml -o /mnt/backups/chatto-daily.tar.gzEncrypted backups
Section titled “Encrypted backups”Use --encrypt to protect the backup archive with a passphrase. For small self-hosted servers, also pass --include-keys so the archive is self-contained:
chatto backup -c chatto.toml --encrypt --include-keysYou’ll be prompted to enter and confirm the passphrase.
Encrypted backups use .tar.gz.age as the file extension. The encryption format is age — a modern, audited file encryption tool. This means you can also inspect or decrypt your backups using the standalone age CLI if needed (see Using the age CLI below).
For scripted or automated backups, pass the passphrase via flag or pipe it from stdin:
chatto backup -c chatto.toml --encrypt --include-keys --passphrase "$BACKUP_PASSPHRASE"
# Or pipe from a secrets managervault kv get -field=passphrase secret/chatto | chatto backup -c chatto.toml --encrypt --include-keysRestoring a backup
Section titled “Restoring a backup”-
Stop the Chatto server
Ensure no other process is accessing the NATS data directory.
-
Run the restore command
Terminal window chatto restore backup.tar.gz -c chatto.tomlFor encrypted backups, encryption is auto-detected — you’ll be prompted for the passphrase:
Terminal window chatto restore backup.tar.gz.age -c chatto.tomlYou can also pass the passphrase via flag:
Terminal window chatto restore backup.tar.gz.age -c chatto.toml --passphrase "$BACKUP_PASSPHRASE" -
Start Chatto normally
Terminal window chatto run -c chatto.toml
Conflict handling
Section titled “Conflict handling”By default, restore fails if any stream already exists. Use --conflict to change this:
| Flag | Behavior | Use case |
|---|---|---|
--conflict=error | Fail if any stream exists (default) | Fresh server restore |
--conflict=skip | Skip existing streams | Partial restore / merge |
--conflict=overwrite | Delete and recreate existing streams | Full restore over existing data |
# Restore missing streams only, keep existing datachatto restore backup.tar.gz -c chatto.toml --conflict=skip
# Full overwrite (destructive!)chatto restore backup.tar.gz -c chatto.toml --conflict=overwriteAdvanced: separate key backup
Section titled “Advanced: separate key backup”By default, encryption key records are excluded from data backups. This keeps leaked data archives unable to decrypt their own message bodies or durable user PII.
Most small self-hosted servers should use chatto backup --encrypt --include-keys instead. Use separate key exports only when you intentionally want data archives and key material stored or retained separately.
To fully restore accounts, profile indexes, and messages from a backup that does not include keys, export the KEK records separately. The wrapped app DEK records live in RUNTIME_STATE and are included in the normal data backup.
Exporting keys
Section titled “Exporting keys”chatto keys export -c chatto.toml -o keys.backupYou’ll be prompted for a passphrase. The export file is encrypted using age, so it’s safe to store — but use a strong passphrase.
For scripted/automated backups, pass the passphrase via flag or pipe it from stdin:
chatto keys export -c chatto.toml -o keys.backup --passphrase "$KEY_BACKUP_PASSPHRASE"
# Or pipe from a secrets managervault kv get -field=passphrase secret/chatto | chatto keys export -c chatto.toml -o keys.backupImporting keys
Section titled “Importing keys”After restoring a data backup, import the encryption keys:
chatto keys import keys.backup -c chatto.tomlExisting key records are never overwritten — only refs that do not already exist are imported. This makes it safe to re-run.
Complete disaster recovery
Section titled “Complete disaster recovery”For a full restore from a monolithic encrypted backup:
-
Restore the backup
Terminal window chatto restore backup.tar.gz.age -c chatto.toml -
Start the server
Terminal window chatto run -c chatto.toml
If your backup does not include keys, import the key export after restoring the data archive:
chatto keys import keys.backup -c chatto.tomlAutomated backup script
Section titled “Automated backup script”Here’s a complete monolithic backup script suitable for a cron job:
#!/bin/bashset -euo pipefail
CONFIG="/etc/chatto/chatto.toml"BACKUP_DIR="/mnt/backups/chatto"PASSPHRASE_FILE="/etc/chatto/backup-passphrase" # chmod 600RETENTION_DAYS=14
# Read passphrase from filePASSPHRASE=$(cat "$PASSPHRASE_FILE")
# Create timestamped encrypted backup with keys includedTIMESTAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)BACKUP_FILE="$BACKUP_DIR/$TIMESTAMP.tar.gz.age"
chatto backup -c "$CONFIG" --encrypt --include-keys --passphrase "$PASSPHRASE" -o "$BACKUP_FILE"
# Remove backups older than retention periodfind "$BACKUP_DIR" -name "*.age" -mtime +$RETENTION_DAYS -delete
echo "Backup complete: $BACKUP_FILE"For split data and key backups, run a key export with the same retention policy:
chatto backup -c "$CONFIG" --encrypt --passphrase "$PASSPHRASE" -o "$BACKUP_FILE"chatto keys export -c "$CONFIG" -o "$BACKUP_DIR/$TIMESTAMP-keys.age" --passphrase "$PASSPHRASE"Using the age CLI
Section titled “Using the age CLI”All Chatto encrypted files (backups and key exports) use the standard age format. This means you can work with them using the age CLI tool, which is useful for verification, key rotation, or emergency access.
Installing age
Section titled “Installing age”# macOSbrew install age
# Debian/Ubuntuapt install age
# From sourcego install filippo.io/age/cmd/...@latestDecrypting a backup manually
Section titled “Decrypting a backup manually”If you need to access a backup without chatto restore — for example, to inspect the manifest or extract specific files:
# Decrypt the archive (prompts for passphrase)age -d backup.tar.gz.age > backup.tar.gz
# Then extract normallytar xzf backup.tar.gz
# Inspect the manifestcat backup/manifest.json | jq .Decrypting a key export manually
Section titled “Decrypting a key export manually”# Decrypt the key export (prompts for passphrase)age -d keys.backup > keys.json
# View the exported keys (careful — contains sensitive data!)cat keys.json | jq .Re-encrypting with a different passphrase
Section titled “Re-encrypting with a different passphrase”# Decrypt with the old passphraseage -d backup.tar.gz.age > backup.tar.gz
# Re-encrypt with a new passphraseage -p backup.tar.gz > backup-new.tar.gz.age
# Clean up the unencrypted filerm backup.tar.gzBackup strategy recommendations
Section titled “Backup strategy recommendations”Frequency: Schedule backups based on your tolerance for data loss. For active servers, daily backups are a reasonable starting point.
Retention: Keep multiple backup archives, but do not keep them forever. A common pattern is daily backups retained for 14 days, plus weekly backups retained for 8 weeks. If you use split data and key backups, apply the same retention window to both files.
Storage: Store backups on a different volume or remote object storage, such as AWS S3, Cloudflare R2, Wasabi, or Backblaze B2. A backup on the same disk as the data doesn’t protect against disk failure.
Encryption: Always use --encrypt for backups stored on shared or remote storage. Even though message bodies are already encrypted, backups contain sensitive metadata (user info, room structure, membership lists).
Key separation: Use a single encrypted backup with --include-keys unless you have a clear reason to split data and key material. Split backups add defense in depth, but they also add an extra file that must be backed up, retained, and restored correctly.
Testing: Periodically test restores to a separate server to verify your backups are valid and your passphrase works.
Retention and account deletion
Section titled “Retention and account deletion”Account deletion shreds the user’s encryption keys in the live server and removes message-owned assets from active storage. Existing backups are not modified.
If you restore a backup from before an account deletion and that backup includes the user’s keys, the deleted user’s encrypted message bodies and durable profile data can become readable again. The same is true when restoring a keyless data backup together with an old key export that still contains that user’s keys. This is expected disaster-recovery behavior, but it matters for erasure requests.
For self-hosted servers, retention is an operator policy:
- If you use split data and key backups, apply the same retention window to Chatto backup archives and
chatto keys exportfiles. - Configure lifecycle rules on remote backup buckets instead of relying on Chatto to prune cloud storage.
- If your backup bucket uses object versioning, expire noncurrent versions as well as current objects.
- Include storage-provider snapshots, disk snapshots, and replicated backup buckets in your retention runbook.
For S3-backed assets, Chatto deletes active asset objects when account deletion removes avatars, attachments, and derivatives. Bucket snapshots, object versions, and external backup copies are still controlled by your storage provider’s lifecycle policy.