# Backup & Recovery

> Learn how to safely backup and restore your Broadcast installation data to protect against data loss.

Source: https://sendbroadcast.net/docs/backup

Regular backups are essential to protect your data and ensure you can recover from unexpected issues. This guide covers backing up your Broadcast installation and restoring from backups when needed.

### Important: Backup Before Upgrades

Always create a backup before upgrading, downgrading, or making significant changes to your Broadcast installation. This ensures you can restore your data if anything goes wrong during the process.

## What Gets Backed Up

A complete Broadcast backup includes:

- **Database**: All your subscribers, broadcasts, sequences, templates, and settings
- **File uploads**: Images, attachments, and other files uploaded to your installation
- **Configuration files**: Your environment settings and custom configurations

## Automatic Installation Backup

If you installed Broadcast using the automatic installation method, backups are handled automatically.

### Creating a Manual Backup

To create a backup manually:

1. Go to **Application** → **Backups** in your Broadcast interface
2. Click **Create backup**
3. Wait for the backup process to complete (a few minutes for most installations; large databases take longer)
4. Download the backup file when prompted

Access to the Backups page requires the **system backups permission** (or a sudo account), see your user's system permissions if the page shows an access denied message.

### How the Create Backup Button Works

The **Create Backup** button requires a *managed* installation (installed with the automatic installer, which sets `BROADCAST_MANAGED`). Behind the scenes:

1. The app writes a trigger file that the host's cron-driven watcher picks up
2. The host script runs `pg_dump`, packages the tarball with a version file and a `.sha256` checksum, and places it in the app's storage
3. The Backups page reconciles the finished file into your backup history

Because of this architecture, two things must be true for backups to complete: the host cron jobs installed by the automatic installer must be running, and the app's **background job system (SolidQueue) must be up**: it powers the stale-backup cleanup and object storage uploads. If a backup stays "Pending" for over an hour, check **Application → Jobs** for a running job system.

On unmanaged (manual Docker) installations the button instead shows the equivalent command to run on your host.

### Automatic Backups

**Important Note**: Automatic scheduled backups are not currently implemented in the standard installation. The installation sets up the infrastructure for backups but does not schedule automatic database backups.

However, the system does include:

- Manual backup capability via command line
- Backup retention (keeps only the most recent backup file)
- Stores backups in `/opt/broadcast/db/backups/`
- Can be accessed through the Broadcast interface when created

### Command Line Backup

You can also create backups via command line by SSH'ing into your server as `root`:

```bash
# Navigate to the Broadcast directory
cd /opt/broadcast

# Create a database backup (the primary backup method)
./broadcast.sh backup_database

# The backup will be saved to db/backups/ with a timestamp and also copied to app/storage
# Note: The full backup command (./broadcast.sh backup) is not yet implemented
```

## Backing Up to S3-Compatible Object Storage

Every backup is stored on your server's local disk. You can additionally upload each new backup to S3-compatible object storage: AWS S3, Cloudflare R2, Backblaze B2, Wasabi, MinIO, or any S3-compatible bucket, so a copy survives the loss of the server itself.

Note

This destination is only for backups. It is configured separately from where your uploaded images live day to day, which is set under Application → Storage. The two can point at the same provider or at different ones. See [Storage](https://sendbroadcast.net/docs/storage).

The **Backup destinations** panel shows both destinations at a glance, and each row in the history below carries the upload state of that backup's remote copy:

![The Backups page showing local disk and S3-compatible object storage destinations, and a backup history where rows show Uploaded to object storage, Uploading, and a failed upload with its error](https://sendbroadcast.net/assets/docs/backups/backups-page-destinations-53a80270.png)

### Configuring a Destination

1. Go to **Application** → **Backups**
2. In the **Backup destinations** panel, click **Configure**
3. Fill in your bucket details (see provider notes below)
4. Click **Test connection**. This verifies your settings in three steps: settings complete, bucket reachable, and **write & delete permissions** (a tiny probe object is written and removed, proving the exact permissions uploads need). You can test before saving; testing never stores anything.
5. When all three checks pass, click **Save** and enable **Upload new backups to this bucket**

![The backup storage settings drawer with bucket, region, endpoint, key prefix and access key fields, and Test connection, Cancel and Save actions](https://sendbroadcast.net/assets/docs/backups/backup-storage-drawer-5538e243.png)

Credentials are stored encrypted; a saved secret key is never displayed again (leave the field blank to keep it when editing).

### Provider Notes

| Provider | Endpoint | Region |
|---|---|---|
| AWS S3 | leave blank | your bucket's region, e.g. `us-east-1` |
| Cloudflare R2 | `https://.r2.cloudflarestorage.com` | `auto` | | Backblaze B2 | `https://s3..backblazeb2.com` | your B2 region | | Wasabi | `https://s3..wasabisys.com` | your Wasabi region | | MinIO / other | your server's URL | any value, e.g. `us-east-1` | **Credentials should be scoped to one bucket.** For Cloudflare R2, create an API token under **R2 → Manage R2 API Tokens** with *Object Read & Write* permission applied to your backup bucket only: it provides the S3 access key ID and secret. For AWS S3, use a dedicated IAM user with a policy like:
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::your-backup-bucket",
        "arn:aws:s3:::your-backup-bucket/*"
      ]
    }
  ]
}
```

 ### How Uploads Behave - Each completed backup uploads automatically in the background; its row on the Backups page shows the upload state (**Upload pending → Uploading → Uploaded to object storage**). - An upload failure never affects the backup itself: the local copy is intact, the row shows the error, and a retry button re-queues the upload once you've fixed the cause. - **Remote retention** is independent of local retention: after each successful upload, only the newest N copies (your "Remote copies to keep" setting) remain in the bucket. - Deleting a backup in Broadcast also removes its copy from the bucket. - A SHA-256 checksum of every backup is recorded at completion, so copies can be verified. ### Downloading When the Local File Is Gone If a backup's local file no longer exists but its uploaded copy does, the **Download** button still works: it fetches directly from your bucket via a short-lived signed link. This is the disaster-recovery path: even with the server's disk lost, backups remain retrievable from object storage (via Broadcast on a new server, or straight from your provider's console). ## Manual Installation Backup If you installed Broadcast manually using Docker, you'll need to handle backups yourself. ### Database Backup Create a PostgreSQL dump of your database:
```bash
# Replace 'broadcast' with your database name if different
docker exec postgres pg_dump -U broadcast broadcast_primary_production \
  > backup_$(date +%Y%m%d_%H%M%S).sql

# For a compressed backup
docker exec postgres pg_dump -U broadcast broadcast_primary_production \
  | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
```

 ### File Backup Back up your uploaded files and storage:
```bash
# Replace paths with your actual volume mount paths
tar -czf files_backup_$(date +%Y%m%d_%H%M%S).tar.gz \
  /path/to/broadcast/storage /path/to/broadcast/uploads
```

 ### Complete System Backup For a complete backup, combine database and files:
```bash
#!/bin/bash
# backup-broadcast.sh

BACKUP_DIR="/home/your-user/broadcast-backups"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p "$BACKUP_DIR"

# Database backup
docker exec postgres pg_dump -U broadcast broadcast_primary_production \
  | gzip > "$BACKUP_DIR/database_$DATE.sql.gz"

# Files backup
tar -czf "$BACKUP_DIR/files_$DATE.tar.gz" \
  /path/to/broadcast/storage /path/to/broadcast/uploads

# Configuration backup
tar -czf "$BACKUP_DIR/config_$DATE.tar.gz" \
  /path/to/broadcast/app/.env /path/to/broadcast/db/.env

echo "Backup completed: $BACKUP_DIR"
ls -la "$BACKUP_DIR"
```

 Make the script executable and run it:
```bash
chmod +x backup-broadcast.sh
./backup-broadcast.sh
```

 ## Restoring a Backup Restoring a backup replaces all data in your database with the data from the backup file. This is typically needed when: - **Migrating to a new server** - Moving your Broadcast installation to different hardware - **Disaster recovery** - Recovering from data loss or corruption - **Rolling back** - Reverting to a previous state after a problematic change

### Warning

Restoring a backup will **replace all existing data** in your database. This action cannot be undone. Make sure you have a current backup before proceeding.

 ### Version Compatibility Backups include version information to ensure compatibility: - **Same version**: Restore works seamlessly - **Older backup → Newer installation**: Supported. Database migrations will automatically run after restore to update the schema. - **Newer backup → Older installation**: **Not supported.** The restore will be blocked. You must upgrade your installation to match the backup version first. If you're migrating to a new server, we recommend installing the same version of Broadcast as your backup, then upgrading after the restore is complete. ### Step 1: Get Your Backup File If you're migrating to a new server, first download your backup from the old server: 1. Log in to your old Broadcast installation 2. Go to **Application → Backups** 3. Click **Download** on the backup you want to restore Alternatively, use SCP to copy directly between servers:
```bash
scp <user>@<old-server>:/opt/broadcast/db/backups/<backup-file>.tar.gz .
```

 **If the old server is gone** and you had object storage uploads enabled, download the backup from your bucket instead, via your provider's console, or with any S3 client. This is exactly the scenario offsite copies exist for. ### Step 2: Install Broadcast on the Target Server If you haven't already, install Broadcast on your new/target server:
```bash
curl -sSL https://sendbroadcast.net/install | sudo bash
```

 Follow the installation prompts. The database will be empty after installation. ### Step 3: Upload the Backup File Copy your backup file to the target server:
```bash
scp <backup-file>.tar.gz <user>@<new-server>:/opt/broadcast/
```

 ### Step 4: Run the Restore Command SSH into your server and run the restore command:
```bash
cd /opt/broadcast
sudo ./broadcast.sh restore <backup-file>.tar.gz
```

 You will be asked to confirm the restore. Type `yes` and press Enter. (For non-interactive use in scripts or automation, append `--yes` to skip the confirmation.) The restore process will: 1. Verify the backup's integrity against its `.sha256` checksum file, when one exists next to the tarball: a corrupted or incomplete backup is refused before anything is touched 2. Stop the Broadcast application 3. Extract the backup archive and check version compatibility 4. Restore the database using pg_restore 5. Run database migrations (for older backups on newer installations) 6. Restart all services If you downloaded the backup from object storage or copied it between servers, also copy its `.sha256` file (from `/opt/broadcast/db/backups/` on the source) next to the tarball so the integrity check can run. ### Step 5: Verify the Restore After the restore completes: 1. Visit your Broadcast installation in a browser 2. Log in with your credentials from the backup 3. Verify your channels, subscribers, and broadcasts are present

### Note about uploaded files

Database backups include your subscribers, broadcasts, sequences, and configuration. However, if you're using local storage for file uploads (images, attachments), those files are stored separately in `/opt/broadcast/app/storage/` and need to be copied manually.

 ### Restore Troubleshooting **"Backup file not found"** - Ensure the file is in `/opt/broadcast/` or provide the full path - Check the filename matches exactly (including the `.tar.gz` extension) **"Permission denied"** - Run the command with `sudo` - Ensure you're logged in as a user with sudo privileges **Restore completes with warnings** - This is often normal. PostgreSQL reports warnings for objects that don't exist when using `--clean` - Check your data to verify the restore was successful ### Manual Installation Restore For manual installations: 1. **Stop the containers**:
```bash
docker compose -f docker-compose.manual.yml down
```

 2. **Restore the database**:
```bash
# Start only the postgres container
docker compose -f docker-compose.manual.yml up -d postgres

# Wait for postgres to be ready, then restore
gunzip -c database_backup.sql.gz \
  | docker exec -i postgres psql -U broadcast -d broadcast_primary_production
```

 3. **Restore files**:
```bash
tar -xzf files_backup.tar.gz -C /path/to/your/broadcast/
```

 4. **Start all services**:
```bash
docker compose -f docker-compose.manual.yml up -d
```

 ## Automated Backup Scripts ### For Manual Installations Create a cron job to automate backups:
```bash
# Edit your crontab
crontab -e

# Add a line for daily backups at 2 AM
0 2 * * * /path/to/your/backup-broadcast.sh
```

 ### For Automatic Installations If you want to set up automatic scheduled backups for automatic installations, you can add a cron job:
```bash
# Edit the root crontab
sudo crontab -e

# Add a line for daily database backups at 2 AM
0 2 * * * /opt/broadcast/broadcast.sh backup_database \
  >> /opt/broadcast/logs/cron/backup.log 2>&1
```

 ### Backup Retention **Automatic Installations**: The backup script automatically handles retention by keeping only the most recent backup file. Older backups are automatically deleted. **Manual Installations**: To avoid filling up disk space, implement backup retention:
```bash
#!/bin/bash
# cleanup-old-backups.sh

BACKUP_DIR="/home/your-user/broadcast-backups"
DAYS_TO_KEEP=7

# Remove files older than specified days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$DAYS_TO_KEEP -delete
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$DAYS_TO_KEEP -delete

echo "Cleaned up backups older than $DAYS_TO_KEEP days"
```

 ## Best Practices ### Backup Frequency - **Production environments**: Daily automated backups - **Development/testing**: Before major changes or weekly - **Before upgrades**: Always create a backup before upgrading ### Storage Locations - **Local storage**: Quick access but vulnerable to server failures - **Remote storage**: Use the built-in [S3-compatible object storage uploads](#backing-up-to-s3-compatible-object-storage) so every backup automatically gets an offsite copy - **Multiple locations**: The built-in setup gives you both: local disk always, plus the bucket copy when enabled ### Testing Backups Regularly test your backup restoration process: 1. Create a test environment 2. Restore from a recent backup 3. Verify all data is intact 4. Test application functionality ### Security - **Encrypt backups** containing sensitive data - **Secure storage** with appropriate access controls - **Regular rotation** of backup encryption keys ## Troubleshooting ### Common Issues **Database restore fails with permission errors**:
```bash
# Ensure correct ownership
chown -R broadcast:broadcast /opt/broadcast/
```

 **Large backup files**:
```bash
# Use compression for database backups
pg_dump database_name | gzip > backup.sql.gz

# Split large files if needed
split -b 1000m large_backup.tar.gz backup_part_
```

 **Out of disk space**:
```bash
# Check disk usage
df -h

# Clean up old backups
find /backup/path -name "*.sql.gz" -mtime +7 -delete
```

 ### Recovery Planning Create a disaster recovery plan that includes: - **Backup locations** and access procedures - **Recovery time objectives** (how quickly you need to restore) - **Recovery point objectives** (acceptable data loss timeframe) - **Contact information** for technical support - **Step-by-step restoration procedures** ## Getting Help If you need assistance with backup or recovery procedures: - Check the [troubleshooting section](#troubleshooting) above - Review your backup files and error messages - Contact support with specific error details and your installation method Remember: The best backup is the one you never need, but when you do need it, you'll be grateful it exists.
