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:
- Sessions directory — contains Baileys authentication credentials. Loss means every WhatsApp number must be re-linked via QR scan.
- 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
| Data | Location | Criticality | Notes |
|---|---|---|---|
| Sessions | wasphere_sessions Docker volume | Critical | Without this, all numbers need re-linking |
| PostgreSQL | wasphere_postgres_data Docker volume | Critical | All application state |
.env file | Server filesystem | High | Contains secrets — back up encrypted |
docker-compose.yml | Server filesystem | Medium | Can 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.
Custom format dump (recommended for large databases)
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."