Skip to content

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:

Terminal window
chatto backup -c chatto.toml --encrypt --include-keys

This is the easiest backup to restore correctly. Keep the passphrase somewhere safe; anyone with the archive and passphrase can read the restored data.

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

Certain streams are intentionally excluded:

StreamReason
Encryption keysSecurity: excluded by default, unless you pass --include-keys.
User presenceEphemeral (memory-only), reconstructed at runtime
LiveKit E2EE call keysStored in ENCRYPTION_KEYS and excluded unless key backups are explicitly included; active calls cannot be recovered without them
Link preview cacheRegeneratable on demand
Asset cacheRegeneratable (resized image variants)

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 = 4222
bind_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.

Terminal window
# Default: saves to backups/<timestamp>.tar.gz
chatto backup -c chatto.toml
# Custom output path
chatto backup -c chatto.toml -o /mnt/backups/chatto-daily.tar.gz

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:

Terminal window
chatto backup -c chatto.toml --encrypt --include-keys

You’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:

Terminal window
chatto backup -c chatto.toml --encrypt --include-keys --passphrase "$BACKUP_PASSPHRASE"
# Or pipe from a secrets manager
vault kv get -field=passphrase secret/chatto | chatto backup -c chatto.toml --encrypt --include-keys
  1. Stop the Chatto server

    Ensure no other process is accessing the NATS data directory.

  2. Run the restore command

    Terminal window
    chatto restore backup.tar.gz -c chatto.toml

    For encrypted backups, encryption is auto-detected — you’ll be prompted for the passphrase:

    Terminal window
    chatto restore backup.tar.gz.age -c chatto.toml

    You can also pass the passphrase via flag:

    Terminal window
    chatto restore backup.tar.gz.age -c chatto.toml --passphrase "$BACKUP_PASSPHRASE"
  3. Start Chatto normally

    Terminal window
    chatto run -c chatto.toml

By default, restore fails if any stream already exists. Use --conflict to change this:

FlagBehaviorUse case
--conflict=errorFail if any stream exists (default)Fresh server restore
--conflict=skipSkip existing streamsPartial restore / merge
--conflict=overwriteDelete and recreate existing streamsFull restore over existing data
Terminal window
# Restore missing streams only, keep existing data
chatto restore backup.tar.gz -c chatto.toml --conflict=skip
# Full overwrite (destructive!)
chatto restore backup.tar.gz -c chatto.toml --conflict=overwrite

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.

Terminal window
chatto keys export -c chatto.toml -o keys.backup

You’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:

Terminal window
chatto keys export -c chatto.toml -o keys.backup --passphrase "$KEY_BACKUP_PASSPHRASE"
# Or pipe from a secrets manager
vault kv get -field=passphrase secret/chatto | chatto keys export -c chatto.toml -o keys.backup

After restoring a data backup, import the encryption keys:

Terminal window
chatto keys import keys.backup -c chatto.toml

Existing key records are never overwritten — only refs that do not already exist are imported. This makes it safe to re-run.

For a full restore from a monolithic encrypted backup:

  1. Restore the backup

    Terminal window
    chatto restore backup.tar.gz.age -c chatto.toml
  2. 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:

Terminal window
chatto keys import keys.backup -c chatto.toml

Here’s a complete monolithic backup script suitable for a cron job:

#!/bin/bash
set -euo pipefail
CONFIG="/etc/chatto/chatto.toml"
BACKUP_DIR="/mnt/backups/chatto"
PASSPHRASE_FILE="/etc/chatto/backup-passphrase" # chmod 600
RETENTION_DAYS=14
# Read passphrase from file
PASSPHRASE=$(cat "$PASSPHRASE_FILE")
# Create timestamped encrypted backup with keys included
TIMESTAMP=$(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 period
find "$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:

Terminal window
chatto backup -c "$CONFIG" --encrypt --passphrase "$PASSPHRASE" -o "$BACKUP_FILE"
chatto keys export -c "$CONFIG" -o "$BACKUP_DIR/$TIMESTAMP-keys.age" --passphrase "$PASSPHRASE"

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.

Terminal window
# macOS
brew install age
# Debian/Ubuntu
apt install age
# From source
go install filippo.io/age/cmd/...@latest

If you need to access a backup without chatto restore — for example, to inspect the manifest or extract specific files:

Terminal window
# Decrypt the archive (prompts for passphrase)
age -d backup.tar.gz.age > backup.tar.gz
# Then extract normally
tar xzf backup.tar.gz
# Inspect the manifest
cat backup/manifest.json | jq .
Terminal window
# 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 .
Terminal window
# Decrypt with the old passphrase
age -d backup.tar.gz.age > backup.tar.gz
# Re-encrypt with a new passphrase
age -p backup.tar.gz > backup-new.tar.gz.age
# Clean up the unencrypted file
rm backup.tar.gz

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.

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 export files.
  • 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.