Skip to content

Backup & Restore

Chatto provides built-in CLI commands for backing up and restoring all instance data. Backups capture the full state of your NATS JetStream storage — users, spaces, rooms, messages, memberships, assets, and more — into a single .tar.gz archive. Backups can optionally be encrypted with a passphrase using age encryption.

Backups contain all persistent JetStream streams, KV buckets, and object stores:

  • Instance data — users, spaces, memberships, roles, permissions, notifications
  • Per-space data — rooms, messages, threads, reactions, message bodies
  • Assets — user avatars, space icons, message attachments

Certain streams are intentionally excluded:

StreamReason
Encryption keysSecurity: keeps backup data encrypted at rest. Back up keys separately.
User presenceEphemeral (memory-only), reconstructed at runtime
Call stateEphemeral (memory-only), reconstructed by LiveKit webhooks
Link preview cacheRegeneratable on demand
Asset cacheRegeneratable (resized image variants)
Auth tokensSecurity: prevents session token leakage via backup files

Run chatto backup while your Chatto instance is running. The command connects to NATS via the client configuration in chatto.toml.

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. You’ll be prompted to enter and confirm the passphrase:

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

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 --passphrase "$BACKUP_PASSPHRASE"
# Or pipe from a secrets manager
vault kv get -field=passphrase secret/chatto | chatto backup -c chatto.toml --encrypt
  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 start -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 instance 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

Encryption keys are excluded from data backups by design — since Chatto always encrypts message bodies, this ensures that a leaked backup archive doesn’t expose message content. To fully restore messages, you need to export the keys separately.

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 keys are never overwritten — only keys for users that don’t already have one are imported. This makes it safe to re-run.

For a full restore with encrypted message recovery:

  1. Restore the data backup

    Terminal window
    chatto restore backup.tar.gz.age -c chatto.toml
  2. Import the encryption keys

    Terminal window
    chatto keys import keys.backup -c chatto.toml
  3. Start the server

    Terminal window
    chatto start -c chatto.toml

Here’s a complete example 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 backup
TIMESTAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)
BACKUP_FILE="$BACKUP_DIR/$TIMESTAMP.tar.gz.age"
chatto backup -c "$CONFIG" --encrypt --passphrase "$PASSPHRASE" -o "$BACKUP_FILE"
chatto keys export -c "$CONFIG" -o "$BACKUP_DIR/$TIMESTAMP-keys.age" --passphrase "$PASSPHRASE"
# Remove backups older than retention period
find "$BACKUP_DIR" -name "*.age" -mtime +$RETENTION_DAYS -delete
echo "Backup complete: $BACKUP_FILE"

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 instances, daily backups are a reasonable starting point.

Retention: Keep multiple backup archives. A common pattern is daily backups retained for 14 days, plus weekly backups retained for 8 weeks.

Storage: Store backups on a different volume or remote storage (S3, GCS) from the NATS data directory. 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, space structure, membership lists).

Key separation: Store key exports separately from data backups when possible. The two files together unlock everything — keeping them in different locations adds defense in depth.

Testing: Periodically test restores to a separate instance to verify your backups are valid and your passphrase works.