WWaSphere Docs
Operations

Backup & Restore

How to back up WaSphere sessions, PostgreSQL data, and restore from backup.

Backup & Restore

WaSphere has two categories of data that need to be backed up:

  1. Sessions directory — contains Baileys authentication credentials. Loss means every WhatsApp number must be re-linked via QR scan.
  2. PostgreSQL database — contains all application data: users, session metadata, message history, API keys, webhook configurations, and job records.

Redis data (BullMQ queues) is ephemeral — jobs are re-created from PostgreSQL state on restart, so Redis does not need to be backed up.

The sessions directory contains raw WhatsApp authentication keys. Treat backups with the same sensitivity as private keys or passwords — encrypt them at rest and restrict access.

What to Back Up

DataLocationCriticalityNotes
Sessionswasphere_sessions Docker volumeCriticalWithout this, all numbers need re-linking
PostgreSQLwasphere_postgres_data Docker volumeCriticalAll application state
.env fileServer filesystemHighContains secrets — back up encrypted
docker-compose.ymlServer filesystemMediumCan be re-cloned from git

Backing Up Sessions

One-time manual backup

# Stop WA Server first to ensure credentials are not mid-write
docker compose stop wa-server

# Create a compressed tar archive of the sessions volume
docker run --rm \
  -v wasphere_sessions:/sessions \
  -v "$(pwd)":/backup \
  alpine \
  tar czf /backup/sessions-$(date +%Y%m%d-%H%M%S).tar.gz -C /sessions .

# Restart WA Server
docker compose start wa-server

The backup file sessions-20260525-103000.tar.gz appears in your current directory.

Always stop the WA Server before backing up sessions. Baileys writes multiple files atomically — backing up mid-write can produce a corrupted credential set.

Automated daily backup (cron)

Create /opt/wasphere-backup/backup.sh:

#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/opt/wasphere-backup/archives"
COMPOSE_DIR="/opt/wasphere"  # Update to your actual wasphere directory
RETENTION_DAYS=30

mkdir -p "$BACKUP_DIR"

echo "[$(date)] Starting WaSphere backup..."

# Stop WA Server briefly
docker compose -f "$COMPOSE_DIR/docker-compose.yml" stop wa-server

# Backup sessions
docker run --rm \
  -v wasphere_sessions:/sessions \
  -v "$BACKUP_DIR":/backup \
  alpine \
  tar czf "/backup/sessions-$(date +%Y%m%d-%H%M%S).tar.gz" -C /sessions .

# Restart WA Server
docker compose -f "$COMPOSE_DIR/docker-compose.yml" start wa-server

echo "[$(date)] Sessions backup complete."

# Backup PostgreSQL (hot backup — no downtime required)
docker compose -f "$COMPOSE_DIR/docker-compose.yml" exec -T postgres \
  pg_dump -U wasphere -Fc wasphere \
  > "$BACKUP_DIR/postgres-$(date +%Y%m%d-%H%M%S).dump"

echo "[$(date)] PostgreSQL backup complete."

# Delete old backups
find "$BACKUP_DIR" -name "*.tar.gz" -mtime "+$RETENTION_DAYS" -delete
find "$BACKUP_DIR" -name "*.dump" -mtime "+$RETENTION_DAYS" -delete

echo "[$(date)] Backup complete. Files in $BACKUP_DIR:"
ls -lh "$BACKUP_DIR"

Make it executable and add to cron:

chmod +x /opt/wasphere-backup/backup.sh

# Run daily at 3 AM
crontab -e
# Add this line:
0 3 * * * /opt/wasphere-backup/backup.sh >> /var/log/wasphere-backup.log 2>&1

Offsite backup

After the local backup, copy to an S3-compatible object store:

# Using AWS CLI (or MinIO, Backblaze B2, etc.)
aws s3 sync /opt/wasphere-backup/archives/ \
  s3://your-backup-bucket/wasphere/ \
  --storage-class STANDARD_IA

# Or using rclone for any provider
rclone copy /opt/wasphere-backup/archives/ backblaze:your-bucket/wasphere/

Backing Up PostgreSQL

Manual dump (plain SQL format)

docker compose exec postgres \
  pg_dump -U wasphere --clean --if-exists wasphere \
  > wasphere-$(date +%Y%m%d).sql

This produces a plain SQL file that can be read and edited.

docker compose exec -T postgres \
  pg_dump -U wasphere -Fc wasphere \
  > wasphere-$(date +%Y%m%d).dump

Custom format (-Fc) is compressed, faster to restore, and supports parallel restore. Recommended for databases over 500 MB.

Verify the dump

# List contents of a custom format dump
pg_restore --list wasphere-20260525.dump | head -20

Restoring from Backup

Restore sessions

# Stop the WA Server
docker compose stop wa-server

# Clear the existing sessions volume
docker run --rm \
  -v wasphere_sessions:/sessions \
  alpine sh -c "rm -rf /sessions/*"

# Extract the backup
docker run --rm \
  -v wasphere_sessions:/sessions \
  -v "$(pwd)":/backup \
  alpine \
  tar xzf /backup/sessions-20260525-103000.tar.gz -C /sessions

# Restart WA Server
docker compose start wa-server

After restore, wait 30–60 seconds for the WA Server to reconnect all sessions. Check the dashboard or run:

docker compose logs wa-server --tail=50

You should see lines like Session sess_abc123 reconnected successfully.

Restore PostgreSQL

Restoring PostgreSQL overwrites all existing data. Only restore on a fresh installation or if you intend to replace the current database state entirely.

From custom format dump:

# Stop Dashboard API to prevent writes during restore
docker compose stop dashboard-api dashboard-ui

# Drop and recreate the database
docker compose exec postgres \
  psql -U wasphere -c "DROP DATABASE IF EXISTS wasphere;"
docker compose exec postgres \
  psql -U wasphere -c "CREATE DATABASE wasphere;"

# Restore from dump
docker run --rm \
  -v wasphere_postgres_data:/var/lib/postgresql/data \
  -v "$(pwd)":/backup \
  --network wasphere_default \
  postgres:16 \
  pg_restore -h postgres -U wasphere -d wasphere \
  --no-privileges --no-owner \
  /backup/wasphere-20260525.dump

# Restart services
docker compose start dashboard-api dashboard-ui

From plain SQL dump:

docker compose stop dashboard-api dashboard-ui

docker compose exec -T postgres \
  psql -U wasphere wasphere < wasphere-20260525.sql

docker compose start dashboard-api dashboard-ui

Run migrations after restore

After restoring, ensure the database schema is up to date:

docker compose exec dashboard-api npx prisma migrate deploy

Moving to a New Server

Complete procedure for migrating WaSphere to a different server:

# On the OLD server — create full backup
docker compose stop wa-server
docker run --rm \
  -v wasphere_sessions:/sessions \
  -v "$(pwd)":/backup \
  alpine tar czf /backup/sessions.tar.gz -C /sessions .
docker compose start wa-server

docker compose exec -T postgres \
  pg_dump -U wasphere -Fc wasphere > postgres.dump

# Copy .env and backup files to the new server
scp sessions.tar.gz postgres.dump .env user@new-server:/opt/wasphere/

# On the NEW server — install WaSphere, then restore
cd /opt/wasphere
git clone https://github.com/wasphere/wasphere.git .
# (copy .env from the transfer above, update DOMAIN if it changed)

docker compose up -d postgres redis
sleep 10  # Wait for postgres to be ready

# Restore database
docker compose exec -T postgres \
  pg_restore -U wasphere -d wasphere /opt/wasphere/postgres.dump

# Run migrations
docker compose run --rm dashboard-api npx prisma migrate deploy

# Restore sessions
docker run --rm \
  -v wasphere_sessions:/sessions \
  -v "$(pwd)":/backup \
  alpine tar xzf /backup/sessions.tar.gz -C /sessions

# Start all services
docker compose up -d

Update your DNS A record to point the domain to the new server IP. Once DNS propagates, Traefik will provision a new certificate automatically.

Backup Verification

Regularly test that your backups are restorable. A backup that hasn't been tested isn't a backup.

Monthly restore drill

#!/usr/bin/env bash
# Test restore to a temporary Docker environment

BACKUP_FILE="/opt/wasphere-backup/archives/postgres-latest.dump"
TEST_CONTAINER="wasphere_restore_test"

docker run --rm --name "$TEST_CONTAINER" \
  -e POSTGRES_USER=wasphere \
  -e POSTGRES_PASSWORD=test \
  -e POSTGRES_DB=wasphere \
  -d postgres:16

sleep 5

docker cp "$BACKUP_FILE" "$TEST_CONTAINER:/backup.dump"
docker exec "$TEST_CONTAINER" \
  pg_restore -U wasphere -d wasphere /backup.dump

# Verify key tables exist
docker exec "$TEST_CONTAINER" \
  psql -U wasphere -c "SELECT COUNT(*) FROM sessions; SELECT COUNT(*) FROM users;"

docker stop "$TEST_CONTAINER"
echo "Restore test passed."

On this page