879 lines
24 KiB
Markdown
879 lines
24 KiB
Markdown
---
|
||
title: Mailcow Backup and Restore Strategy
|
||
description: Mailcow backup
|
||
published: true
|
||
date: 2026-02-13T22:23:40.797Z
|
||
tags:
|
||
editor: markdown
|
||
dateCreated: 2026-02-11T01:20:59.127Z
|
||
---
|
||
|
||
# Mailcow Backup and Recovery Guide
|
||
|
||
## Overview
|
||
|
||
This document provides comprehensive backup and recovery procedures for Mailcow email server. Since Mailcow is **not running on ZFS or BTRFS**, snapshots are not available and we rely on Mailcow's native backup script combined with Kopia for offsite storage in vaults.
|
||
|
||
## Quick Reference
|
||
|
||
### Common Backup Commands
|
||
|
||
```bash
|
||
# Run a manual backup (all components)
|
||
cd /opt/mailcow-dockerized
|
||
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||
|
||
# Backup with multithreading (faster)
|
||
THREADS=4 MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||
|
||
# List Kopia snapshots
|
||
kopia snapshot list --tags mailcow
|
||
|
||
# View backup logs
|
||
tail -f /var/log/mailcow-backup.log
|
||
```
|
||
|
||
### Common Restore Commands
|
||
|
||
```bash
|
||
# Restore using mailcow native script (interactive)
|
||
cd /opt/mailcow-dockerized
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
|
||
# Restore from Kopia to new server
|
||
kopia snapshot list --tags tier1-backup
|
||
kopia restore <snapshot-id> /opt/mailcow-backups/
|
||
|
||
# Check container status after restore
|
||
docker compose ps
|
||
docker compose logs -f
|
||
```
|
||
|
||
## Critical Components to Backup
|
||
|
||
### 1. Docker Compose File
|
||
- **Location**: `/opt/mailcow-dockerized/docker-compose.yml` (or your installation path)
|
||
- **Purpose**: Defines all containers, networks, and volumes
|
||
- **Importance**: Critical for recreating the exact container configuration
|
||
|
||
### 2. Configuration Files
|
||
- **Primary Config**: `/opt/mailcow-dockerized/mailcow.conf`
|
||
- **Additional Configs**:
|
||
- `/opt/mailcow-dockerized/data/conf/` (all subdirectories)
|
||
- Custom SSL certificates if not using Let's Encrypt
|
||
- Any override files (e.g., `docker-compose.override.yml`)
|
||
|
||
### 3. Database
|
||
- **MySQL/MariaDB Data**: Contains all mailbox configurations, users, domains, aliases, settings
|
||
- **Docker Volume**: `mailcowdockerized_mysql-vol`
|
||
- **Container Path**: `/var/lib/mysql`
|
||
|
||
### 4. Email Data
|
||
- **Maildir Storage**: All actual email messages
|
||
- **Docker Volume**: `mailcowdockerized_vmail-vol`
|
||
- **Container Path**: `/var/vmail`
|
||
- **Size**: Typically the largest component
|
||
|
||
### 5. Additional Important Data
|
||
- **Redis Data**: `mailcowdockerized_redis-vol` (cache and sessions)
|
||
- **Rspamd Data**: `mailcowdockerized_rspamd-vol` (spam learning)
|
||
- **Crypt Data**: `mailcowdockerized_crypt-vol` (if using mailbox encryption)
|
||
- **Postfix Queue**: `mailcowdockerized_postfix-vol` (queued/deferred mail)
|
||
|
||
## Backup Strategy
|
||
|
||
### Two-Tier Backup Approach
|
||
|
||
We use a **two-tier approach** combining Mailcow's native backup script with Kopia for offsite storage:
|
||
|
||
1. **Tier 1 (Local)**: Mailcow's `backup_and_restore.sh` script creates consistent, component-level backups
|
||
2. **Tier 2 (Offsite)**: Kopia snapshots the local backups and syncs to vaults
|
||
|
||
#### Why This Approach?
|
||
|
||
- **Best of both worlds**: Native script ensures mailcow-specific consistency, Kopia provides deduplication and offsite protection
|
||
- **Component-level restore**: Can restore individual components (just vmail, just mysql, etc.) using mailcow script
|
||
- **Disaster recovery**: Full system restore from Kopia backups on new server
|
||
- **Efficient storage**: Kopia's deduplication reduces storage needs for offsite copies
|
||
|
||
#### Backup Frequency
|
||
- **Daily**: Mailcow native backup runs at 2 AM
|
||
- **Daily**: Kopia snapshot of backups runs at 3 AM
|
||
- **Retention (Local)**: 7 days of mailcow backups (managed by script)
|
||
- **Retention (Kopia/Offsite)**: 30 daily, 12 weekly, 12 monthly
|
||
|
||
### Mailcow Native Backup Script
|
||
|
||
Mailcow includes `/opt/mailcow-dockerized/helper-scripts/backup_and_restore.sh` which handles:
|
||
- **vmail**: Email data (mailboxes)
|
||
- **mysql**: Database (using mariabackup for consistency)
|
||
- **redis**: Redis database
|
||
- **rspamd**: Spam filter learning data
|
||
- **crypt**: Encryption data
|
||
- **postfix**: Mail queue
|
||
|
||
**Key Features:**
|
||
- Uses `mariabackup` (hot backup without stopping MySQL)
|
||
- Supports multithreading for faster backups
|
||
- Architecture-aware (handles x86/ARM differences)
|
||
- Built-in cleanup with `--delete-days` parameter
|
||
- Creates compressed archives (.tar.zst or .tar.gz)
|
||
|
||
### Setting Up Mailcow Backups
|
||
|
||
|
||
#### Prereq:
|
||
Make sure you are connected to the repository,
|
||
|
||
```bash
|
||
sudo kopia repository connect server --url=https://192.168.5.10:51516 --override-username=admin --server-cert-fingerprint=696a4999f594b5273a174fd7cab677d8dd1628f9b9d27e557daa87103ee064b2
|
||
```
|
||
|
||
|
||
#### Step 1: Configure Backup Location
|
||
|
||
Set the backup destination via environment variable or in mailcow.conf:
|
||
|
||
```bash
|
||
# Option 1: Set environment variable (preferred for automation)
|
||
export MAILCOW_BACKUP_LOCATION="/opt/mailcow-backups"
|
||
|
||
# Option 2: Add to cron job directly (shown in automated script below)
|
||
```
|
||
|
||
Create the backup directory:
|
||
```bash
|
||
mkdir -p /opt/mailcow-backups
|
||
chown -R root:root /opt/mailcow-backups
|
||
chmod 777 /opt/mailcow-backups
|
||
```
|
||
|
||
#### Step 2: Manual Backup Commands
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Backup all components, delete backups older than 7 days
|
||
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||
|
||
# Backup with multithreading (faster for large mailboxes)
|
||
THREADS=4 MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||
./helper-scripts/backup_and_restore.sh backup all --delete-days 7
|
||
|
||
# Backup specific components only
|
||
MAILCOW_BACKUP_LOCATION=/opt/mailcow-backups \
|
||
./helper-scripts/backup_and_restore.sh backup vmail mysql --delete-days 7
|
||
```
|
||
|
||
**What gets created:**
|
||
- Backup directory: `/opt/mailcow-backups/mailcow-YYYY-MM-DD-HH-MM-SS/`
|
||
- Contains: `.tar.zst` compressed archives for each component
|
||
- Plus: `mailcow.conf` copy for restore reference
|
||
|
||
#### Step 3: Automated Backup Script
|
||
|
||
Create `/opt/scripts/backup-mailcow.sh`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
# Mailcow Automated Backup Script
|
||
# This creates mailcow native backups, then snapshots them with Kopia for offsite storage
|
||
|
||
set -e
|
||
|
||
BACKUP_DATE=$(date +%Y%m%d_%H%M%S)
|
||
LOG_FILE="/var/log/mailcow-backup.log"
|
||
MAILCOW_DIR="/opt/mailcow-dockerized"
|
||
BACKUP_DIR="/opt/mailcow-backups"
|
||
THREADS=4 # Adjust based on your CPU cores
|
||
KEEP_DAYS=7 # Keep local mailcow backups for 7 days
|
||
|
||
echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"
|
||
echo "[${BACKUP_DATE}] Starting Mailcow backup process" | tee -a "$LOG_FILE"
|
||
|
||
# Step 1: Run mailcow's native backup script
|
||
echo "[${BACKUP_DATE}] Running mailcow native backup..." | tee -a "$LOG_FILE"
|
||
|
||
cd "$MAILCOW_DIR"
|
||
|
||
# Run the backup with multithreading
|
||
THREADS=${THREADS} MAILCOW_BACKUP_LOCATION=${BACKUP_DIR} \
|
||
./helper-scripts/backup_and_restore.sh backup all --delete-days ${KEEP_DAYS} \
|
||
2>&1 | tee -a "$LOG_FILE"
|
||
|
||
BACKUP_EXIT=${PIPESTATUS[0]}
|
||
|
||
if [ $BACKUP_EXIT -ne 0 ]; then
|
||
echo "[${BACKUP_DATE}] ERROR: Mailcow backup failed with exit code ${BACKUP_EXIT}" | tee -a "$LOG_FILE"
|
||
exit 1
|
||
fi
|
||
|
||
echo "[${BACKUP_DATE}] Mailcow native backup completed successfully" | tee -a "$LOG_FILE"
|
||
|
||
# Step 2: Create Kopia snapshot of backup directory
|
||
echo "[${BACKUP_DATE}] Creating Kopia snapshot..." | tee -a "$LOG_FILE"
|
||
|
||
kopia snapshot create "${BACKUP_DIR}" \
|
||
--tags mailcow:tier1-backup \
|
||
--description "Mailcow backup ${BACKUP_DATE}" \
|
||
2>&1 | tee -a "$LOG_FILE"
|
||
|
||
KOPIA_EXIT=${PIPESTATUS[0]}
|
||
|
||
if [ $KOPIA_EXIT -ne 0 ]; then
|
||
echo "[${BACKUP_DATE}] WARNING: Kopia snapshot failed with exit code ${KOPIA_EXIT}" | tee -a "$LOG_FILE"
|
||
echo "[${BACKUP_DATE}] Local mailcow backup exists but offsite copy may be incomplete" | tee -a "$LOG_FILE"
|
||
exit 2
|
||
fi
|
||
|
||
echo "[${BACKUP_DATE}] Kopia snapshot completed successfully" | tee -a "$LOG_FILE"
|
||
|
||
# Step 3: Also backup the mailcow installation directory (configs, compose files)
|
||
echo "[${BACKUP_DATE}] Backing up mailcow installation directory..." | tee -a "$LOG_FILE"
|
||
|
||
kopia snapshot create "${MAILCOW_DIR}" \
|
||
--tags mailcow,config,docker-compose \
|
||
--description "Mailcow config ${BACKUP_DATE}" \
|
||
2>&1 | tee -a "$LOG_FILE"
|
||
|
||
echo "[${BACKUP_DATE}] Backup process completed successfully" | tee -a "$LOG_FILE"
|
||
echo "[${BACKUP_DATE}] ========================================" | tee -a "$LOG_FILE"
|
||
|
||
# Optional: Send notification on completion
|
||
# Add your notification method here (email, webhook, etc.)
|
||
```
|
||
|
||
Make it executable:
|
||
```bash
|
||
chmod +x /opt/scripts/backup-mailcow.sh
|
||
```
|
||
|
||
Add to crontab (daily at 2 AM):
|
||
```bash
|
||
# Edit root's crontab
|
||
crontab -e
|
||
|
||
# Add this line:
|
||
0 2 * * * /opt/scripts/backup-mailcow.sh 2>&1 | logger -t mailcow-backup
|
||
```
|
||
|
||
### Offsite Backup to Vaults
|
||
|
||
After local Kopia snapshots are created, sync to your offsite vaults:
|
||
|
||
```bash
|
||
# Option 1: Kopia repository sync (if using multiple Kopia repos)
|
||
kopia repository sync-to filesystem --path /mnt/vault/mailcow-backup
|
||
|
||
# Option 2: Rsync to vault
|
||
rsync -avz --delete /backup/kopia-repo/ /mnt/vault/mailcow-backup/
|
||
|
||
# Option 3: Rclone to remote vault
|
||
rclone sync /backup/kopia-repo/ vault:mailcow-backup/
|
||
```
|
||
|
||
## Recovery Procedures
|
||
|
||
### Understanding Two Recovery Methods
|
||
|
||
We have **two restore methods** depending on the scenario:
|
||
|
||
1. **Mailcow Native Restore** (Preferred): For component-level or same-server recovery
|
||
2. **Kopia Full Restore**: For complete disaster recovery to a new server
|
||
|
||
### Method 1: Mailcow Native Restore (Recommended)
|
||
|
||
Use this method when:
|
||
- Restoring on the same/similar server
|
||
- Restoring specific components (just email, just database, etc.)
|
||
- Recovering from local mailcow backups
|
||
|
||
#### Step 1: List Available Backups
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Run the restore script
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
```
|
||
|
||
The script will prompt:
|
||
```
|
||
Backup location (absolute path, starting with /): /opt/mailcow-backups
|
||
```
|
||
|
||
#### Step 2: Select Backup
|
||
|
||
The script displays available backups:
|
||
```
|
||
Found project name mailcowdockerized
|
||
[ 1 ] - /opt/mailcow-backups/mailcow-2026-02-09-02-00-14/
|
||
[ 2 ] - /opt/mailcow-backups/mailcow-2026-02-10-02-00-08/
|
||
```
|
||
|
||
Enter the number of the backup to restore.
|
||
|
||
#### Step 3: Select Components
|
||
|
||
Choose what to restore:
|
||
```
|
||
[ 0 ] - all
|
||
[ 1 ] - Crypt data
|
||
[ 2 ] - Rspamd data
|
||
[ 3 ] - Mail directory (/var/vmail)
|
||
[ 4 ] - Redis DB
|
||
[ 5 ] - Postfix data
|
||
[ 6 ] - SQL DB
|
||
```
|
||
|
||
**Important**: The script will:
|
||
- Stop mailcow containers automatically
|
||
- Restore selected components
|
||
- Handle permissions correctly
|
||
- Restart containers when done
|
||
|
||
#### Example: Restore Only Email Data
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
|
||
# When prompted:
|
||
# - Backup location: /opt/mailcow-backups
|
||
# - Select backup: 2 (most recent)
|
||
# - Select component: 3 (Mail directory)
|
||
```
|
||
|
||
#### Example: Restore Database Only
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
|
||
# When prompted:
|
||
# - Backup location: /opt/mailcow-backups
|
||
# - Select backup: 2 (most recent)
|
||
# - Select component: 6 (SQL DB)
|
||
```
|
||
|
||
**Note**: For database restore, the script will modify `mailcow.conf` with the database credentials from the backup. Review the changes after restore.
|
||
|
||
### Method 2: Complete Server Rebuild (Kopia Restore)
|
||
|
||
Use this when recovering to a completely new server or when local backups are unavailable.
|
||
|
||
#### Step 1: Prepare New Server
|
||
|
||
```bash
|
||
# Update system
|
||
apt update && apt upgrade -y
|
||
|
||
# Install Docker
|
||
curl -fsSL https://get.docker.com | sh
|
||
systemctl enable docker
|
||
systemctl start docker
|
||
|
||
# Install Docker Compose
|
||
apt install docker-compose-plugin -y
|
||
|
||
# Install Kopia
|
||
curl -s https://kopia.io/signing-key | apt-key add -
|
||
echo "deb https://packages.kopia.io/apt/ stable main" | tee /etc/apt/sources.list.d/kopia.list
|
||
apt update
|
||
apt install kopia -y
|
||
|
||
# Create directory structure
|
||
mkdir -p /opt/mailcow-dockerized
|
||
mkdir -p /opt/mailcow-backups/database
|
||
```
|
||
|
||
#### Step 2: Restore Kopia Repository
|
||
|
||
```bash
|
||
# Connect to your offsite vault
|
||
# If vault is mounted:
|
||
kopia repository connect filesystem --path /mnt/vault/mailcow-backup
|
||
|
||
# If vault is remote:
|
||
kopia repository connect s3 --bucket=your-bucket --access-key=xxx --secret-access-key=xxx
|
||
|
||
# List available snapshots
|
||
kopia snapshot list --tags mailcow
|
||
```
|
||
|
||
#### Step 3: Restore Configuration
|
||
|
||
```bash
|
||
# Find and restore the config snapshot
|
||
kopia snapshot list --tags config
|
||
|
||
# Restore to the Mailcow directory
|
||
kopia restore <snapshot-id> /opt/mailcow-dockerized/
|
||
|
||
# Verify critical files
|
||
ls -la /opt/mailcow-dockerized/mailcow.conf
|
||
ls -la /opt/mailcow-dockerized/docker-compose.yml
|
||
```
|
||
|
||
#### Step 4: Restore Mailcow Backups Directory
|
||
|
||
```bash
|
||
# Restore the entire backup directory from Kopia
|
||
kopia snapshot list --tags tier1-backup
|
||
|
||
# Restore the most recent backup
|
||
kopia restore <snapshot-id> /opt/mailcow-backups/
|
||
|
||
# Verify backups were restored
|
||
ls -la /opt/mailcow-backups/
|
||
```
|
||
|
||
#### Step 5: Run Mailcow Native Restore
|
||
|
||
Now use mailcow's built-in restore script:
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Run the restore script
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
|
||
# When prompted:
|
||
# - Backup location: /opt/mailcow-backups
|
||
# - Select the most recent backup
|
||
# - Select [ 0 ] - all (to restore everything)
|
||
```
|
||
|
||
The script will:
|
||
1. Stop all mailcow containers
|
||
2. Restore all components (vmail, mysql, redis, rspamd, postfix, crypt)
|
||
3. Update mailcow.conf with restored database credentials
|
||
4. Restart all containers
|
||
|
||
**Alternative: Manual Restore** (if you prefer more control)
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Start containers to create volumes
|
||
docker compose up -d --no-start
|
||
docker compose down
|
||
|
||
# Find the most recent backup directory
|
||
LATEST_BACKUP=$(ls -td /opt/mailcow-backups/mailcow-* | head -1)
|
||
echo "Restoring from: $LATEST_BACKUP"
|
||
|
||
# Extract each component manually
|
||
cd "$LATEST_BACKUP"
|
||
|
||
# Restore vmail (email data)
|
||
docker run --rm \
|
||
-v mailcowdockerized_vmail-vol:/backup \
|
||
-v "$PWD":/restore \
|
||
debian:bookworm-slim \
|
||
tar --use-compress-program='zstd -d' -xvf /restore/backup_vmail.tar.zst
|
||
|
||
# Restore MySQL
|
||
docker run --rm \
|
||
-v mailcowdockerized_mysql-vol:/backup \
|
||
-v "$PWD":/restore \
|
||
mariadb:10.11 \
|
||
tar --use-compress-program='zstd -d' -xvf /restore/backup_mysql.tar.zst
|
||
|
||
# Restore Redis
|
||
docker run --rm \
|
||
-v mailcowdockerized_redis-vol:/backup \
|
||
-v "$PWD":/restore \
|
||
debian:bookworm-slim \
|
||
tar --use-compress-program='zstd -d' -xvf /restore/backup_redis.tar.zst
|
||
|
||
# Restore other components similarly (rspamd, postfix, crypt)
|
||
# ...
|
||
|
||
# Copy mailcow.conf from backup
|
||
cp "$LATEST_BACKUP/mailcow.conf" /opt/mailcow-dockerized/mailcow.conf
|
||
```
|
||
|
||
#### Step 6: Start and Verify Mailcow
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Pull latest images (or use versions from backup if preferred)
|
||
docker compose pull
|
||
|
||
# Start all services
|
||
docker compose up -d
|
||
|
||
# Monitor logs
|
||
docker compose logs -f
|
||
```
|
||
|
||
#### Step 7: Post-Restore Verification
|
||
|
||
```bash
|
||
# Check container status
|
||
docker compose ps
|
||
|
||
# Test web interface
|
||
curl -I https://mail.yourdomain.com
|
||
|
||
# Check mail log
|
||
docker compose logs -f postfix-mailcow
|
||
|
||
# Verify database
|
||
docker compose exec mysql-mailcow mysql -u root -p$(grep DBROOT mailcow.conf | cut -d'=' -f2) -e "SHOW DATABASES;"
|
||
|
||
# Check email storage
|
||
docker compose exec dovecot-mailcow ls -lah /var/vmail/
|
||
```
|
||
|
||
### Scenario 2: Restore Individual Mailbox
|
||
|
||
To restore a single user's mailbox without affecting others:
|
||
|
||
#### Option A: Using Mailcow Backups (If Available)
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Temporarily mount the backup
|
||
BACKUP_DIR="/opt/mailcow-backups/mailcow-YYYY-MM-DD-HH-MM-SS"
|
||
|
||
# Extract just the vmail archive to a temporary location
|
||
mkdir -p /tmp/vmail-restore
|
||
cd "$BACKUP_DIR"
|
||
tar --use-compress-program='zstd -d' -xvf backup_vmail.tar.zst -C /tmp/vmail-restore
|
||
|
||
# Find the user's mailbox
|
||
# Structure: /tmp/vmail-restore/var/vmail/domain.com/user/
|
||
ls -la /tmp/vmail-restore/var/vmail/yourdomain.com/
|
||
|
||
# Copy specific mailbox
|
||
rsync -av /tmp/vmail-restore/var/vmail/yourdomain.com/user@domain.com/ \
|
||
/var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||
|
||
# Fix permissions
|
||
docker run --rm \
|
||
-v mailcowdockerized_vmail-vol:/vmail \
|
||
debian:bookworm-slim \
|
||
chown -R 5000:5000 /vmail/yourdomain.com/user@domain.com/
|
||
|
||
# Cleanup
|
||
rm -rf /tmp/vmail-restore
|
||
|
||
# Restart Dovecot to recognize changes
|
||
docker compose restart dovecot-mailcow
|
||
```
|
||
|
||
#### Option B: Using Kopia Snapshot (If Local Backups Unavailable)
|
||
|
||
```bash
|
||
# Mount the vmail snapshot temporarily
|
||
mkdir -p /mnt/restore
|
||
kopia mount <vmail-snapshot-id> /mnt/restore
|
||
|
||
# Find the user's mailbox
|
||
# Structure: /mnt/restore/domain.com/user/
|
||
ls -la /mnt/restore/yourdomain.com/
|
||
|
||
# Copy specific mailbox
|
||
rsync -av /mnt/restore/yourdomain.com/user@domain.com/ \
|
||
/var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||
|
||
# Fix permissions
|
||
chown -R 5000:5000 /var/lib/docker/volumes/mailcowdockerized_vmail-vol/_data/yourdomain.com/user@domain.com/
|
||
|
||
# Unmount
|
||
kopia unmount /mnt/restore
|
||
|
||
# Restart Dovecot to recognize changes
|
||
docker compose restart dovecot-mailcow
|
||
```
|
||
|
||
### Scenario 3: Database Recovery Only
|
||
|
||
If only the database is corrupted but email data is intact:
|
||
|
||
#### Option A: Using Mailcow Native Restore (Recommended)
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Run the restore script
|
||
./helper-scripts/backup_and_restore.sh restore
|
||
|
||
# When prompted:
|
||
# - Backup location: /opt/mailcow-backups
|
||
# - Select the most recent backup
|
||
# - Select [ 6 ] - SQL DB (database only)
|
||
```
|
||
|
||
The script will:
|
||
1. Stop mailcow
|
||
2. Restore the MySQL database from the mariabackup archive
|
||
3. Update mailcow.conf with the restored database credentials
|
||
4. Restart mailcow
|
||
|
||
#### Option B: Manual Database Restore from Kopia
|
||
|
||
If local backups are unavailable:
|
||
|
||
```bash
|
||
cd /opt/mailcow-dockerized
|
||
|
||
# Stop Mailcow
|
||
docker compose down
|
||
|
||
# Start only MySQL
|
||
docker compose up -d mysql-mailcow
|
||
|
||
# Wait for MySQL
|
||
sleep 30
|
||
|
||
# Restore from Kopia database dump
|
||
kopia snapshot list --tags database
|
||
kopia restore <snapshot-id> /tmp/db-restore/
|
||
|
||
# Import the dump
|
||
LATEST_DUMP=$(ls -t /tmp/db-restore/mailcow_*.sql | head -1)
|
||
docker compose exec -T mysql-mailcow mysql -u root -p$(grep DBROOT mailcow.conf | cut -d'=' -f2) < "$LATEST_DUMP"
|
||
|
||
# Start all services
|
||
docker compose down
|
||
docker compose up -d
|
||
|
||
# Verify
|
||
docker compose logs -f
|
||
```
|
||
|
||
### Scenario 4: Configuration Recovery Only
|
||
|
||
If you only need to restore configuration files:
|
||
|
||
#### Option A: From Mailcow Backup
|
||
|
||
```bash
|
||
# Find the most recent backup
|
||
LATEST_BACKUP=$(ls -td /opt/mailcow-backups/mailcow-* | head -1)
|
||
|
||
# Stop Mailcow
|
||
cd /opt/mailcow-dockerized
|
||
docker compose down
|
||
|
||
# Backup current config (just in case)
|
||
cp mailcow.conf mailcow.conf.pre-restore
|
||
cp docker-compose.yml docker-compose.yml.pre-restore
|
||
|
||
# Restore mailcow.conf from backup
|
||
cp "$LATEST_BACKUP/mailcow.conf" ./mailcow.conf
|
||
|
||
# If you also need other config files from data/conf/,
|
||
# you would need to extract them from the backup archives
|
||
|
||
# Restart
|
||
docker compose up -d
|
||
```
|
||
|
||
#### Option B: From Kopia Snapshot
|
||
|
||
```bash
|
||
# Restore config snapshot to temporary location
|
||
kopia restore <config-snapshot-id> /tmp/mailcow-restore/
|
||
|
||
# Stop Mailcow
|
||
cd /opt/mailcow-dockerized
|
||
docker compose down
|
||
|
||
# Backup current config (just in case)
|
||
cp mailcow.conf mailcow.conf.pre-restore
|
||
cp docker-compose.yml docker-compose.yml.pre-restore
|
||
|
||
# Restore specific files
|
||
cp /tmp/mailcow-restore/mailcow.conf ./
|
||
cp /tmp/mailcow-restore/docker-compose.yml ./
|
||
cp -r /tmp/mailcow-restore/data/conf/* ./data/conf/
|
||
|
||
# Restart
|
||
docker compose up -d
|
||
```
|
||
|
||
## Verification and Testing
|
||
|
||
### Regular Backup Verification
|
||
|
||
Perform monthly restore tests to ensure backups are valid:
|
||
|
||
```bash
|
||
# Test restore to temporary location
|
||
mkdir -p /tmp/backup-test
|
||
kopia snapshot list --tags mailcow
|
||
kopia restore <snapshot-id> /tmp/backup-test/
|
||
|
||
# Verify files exist and are readable
|
||
ls -lah /tmp/backup-test/
|
||
cat /tmp/backup-test/mailcow.conf
|
||
|
||
# Cleanup
|
||
rm -rf /tmp/backup-test/
|
||
```
|
||
|
||
### Backup Monitoring Script
|
||
|
||
Create `/opt/scripts/check-mailcow-backup.sh`:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
# Check last backup age
|
||
LAST_BACKUP=$(kopia snapshot list --tags mailcow --json | jq -r '.[0].startTime')
|
||
LAST_BACKUP_EPOCH=$(date -d "$LAST_BACKUP" +%s)
|
||
NOW=$(date +%s)
|
||
AGE_HOURS=$(( ($NOW - $LAST_BACKUP_EPOCH) / 3600 ))
|
||
|
||
if [ $AGE_HOURS -gt 26 ]; then
|
||
echo "WARNING: Last Mailcow backup is $AGE_HOURS hours old"
|
||
# Send alert (email, Slack, etc.)
|
||
exit 1
|
||
else
|
||
echo "OK: Last backup $AGE_HOURS hours ago"
|
||
fi
|
||
```
|
||
|
||
## Disaster Recovery Checklist
|
||
|
||
When disaster strikes, follow this checklist:
|
||
|
||
- [ ] Confirm scope of failure (server, storage, specific component)
|
||
- [ ] Gather server information (hostname, IP, DNS records)
|
||
- [ ] Access offsite backup vault
|
||
- [ ] Provision new server (if needed)
|
||
- [ ] Install Docker and dependencies
|
||
- [ ] Connect to Kopia repository
|
||
- [ ] Restore configurations first
|
||
- [ ] Restore database
|
||
- [ ] Restore email data
|
||
- [ ] Start services and verify
|
||
- [ ] Test email sending/receiving
|
||
- [ ] Verify webmail access
|
||
- [ ] Check DNS records and update if needed
|
||
- [ ] Document any issues encountered
|
||
- [ ] Update recovery procedures based on experience
|
||
|
||
## Important Notes
|
||
|
||
1. **DNS**: Keep DNS records documented separately. Recovery includes updating DNS if server IP changes.
|
||
|
||
2. **SSL Certificates**: Let's Encrypt certificates are in the backup but may need renewal. Mailcow will handle this automatically.
|
||
|
||
3. **Permissions**: Docker volumes have specific UID/GID requirements:
|
||
- vmail: `5000:5000`
|
||
- mysql: `999:999`
|
||
|
||
4. **Testing**: Always test recovery procedures in a lab environment before trusting them in production.
|
||
|
||
5. **Documentation**: Keep this guide and server details in a separate location (printed copy, password manager, etc.).
|
||
|
||
6. **Retention Policy**: Review Kopia retention settings periodically to balance storage costs with recovery needs.
|
||
|
||
## Backup Architecture Notes
|
||
|
||
### Why Two Backup Layers?
|
||
|
||
**Mailcow Native Backups** (Tier 1):
|
||
- ✅ Component-aware (knows about mailcow's structure)
|
||
- ✅ Uses mariabackup for consistent MySQL hot backups
|
||
- ✅ Fast, selective restore (can restore just one component)
|
||
- ✅ Architecture-aware (handles x86/ARM differences)
|
||
- ❌ No deduplication (full copies each time)
|
||
- ❌ Limited to local storage initially
|
||
|
||
**Kopia Snapshots** (Tier 2):
|
||
- ✅ Deduplication and compression
|
||
- ✅ Efficient offsite replication to vaults
|
||
- ✅ Point-in-time recovery across multiple versions
|
||
- ✅ Disaster recovery to completely new infrastructure
|
||
- ❌ Less component-aware (treats as files)
|
||
- ❌ Slower for granular component restore
|
||
|
||
### Storage Efficiency
|
||
|
||
Using this two-tier approach:
|
||
- **Local**: Mailcow creates ~7 days of native backups (may be large, but short retention)
|
||
- **Offsite**: Kopia deduplicates these backups for long-term vault storage (much smaller)
|
||
|
||
Example storage calculation (10GB mailbox):
|
||
- Local: 7 days × 10GB = ~70GB (before compression)
|
||
- Kopia (offsite): First backup ~10GB, subsequent backups only store changes (might be <1GB/day after dedup)
|
||
|
||
### Compression Formats
|
||
|
||
Mailcow's script creates `.tar.zst` (Zstandard) or `.tar.gz` (gzip) files:
|
||
- **Zstandard** (modern): Better compression ratio, faster (recommended)
|
||
- **Gzip** (legacy): Wider compatibility with older systems
|
||
|
||
Verify your backup compression:
|
||
```bash
|
||
ls -lh /opt/mailcow-backups/mailcow-*/
|
||
# Look for .tar.zst (preferred) or .tar.gz
|
||
```
|
||
|
||
### Cross-Architecture Considerations
|
||
|
||
**Important for ARM/x86 Migration**:
|
||
|
||
Mailcow's backup script is architecture-aware. When restoring:
|
||
- **Rspamd data** cannot be restored across different architectures (x86 ↔ ARM)
|
||
- **All other components** (vmail, mysql, redis, postfix, crypt) are architecture-independent
|
||
|
||
If migrating between architectures:
|
||
```bash
|
||
# Restore everything EXCEPT rspamd
|
||
# Select components individually: vmail, mysql, redis, postfix, crypt
|
||
# Skip rspamd - it will rebuild its learning database over time
|
||
```
|
||
|
||
### Testing Your Backups
|
||
|
||
**Monthly Test Protocol**:
|
||
|
||
1. **Verify local backups exist**:
|
||
```bash
|
||
ls -lh /opt/mailcow-backups/
|
||
# Should see recent dated directories
|
||
```
|
||
|
||
2. **Verify Kopia snapshots**:
|
||
```bash
|
||
kopia snapshot list --tags mailcow
|
||
# Should see recent snapshots
|
||
```
|
||
|
||
3. **Test restore in lab** (recommended quarterly):
|
||
- Spin up a test VM
|
||
- Restore from Kopia
|
||
- Run mailcow native restore
|
||
- Verify email delivery and webmail access
|
||
|
||
## Additional Resources
|
||
|
||
- [Mailcow Official Backup Documentation](https://docs.mailcow.email/backup_restore/b_n_r-backup/)
|
||
- [Kopia Documentation](https://kopia.io/docs/)
|
||
- [Docker Volume Backup Best Practices](https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes)
|
||
|
||
## Revision History
|
||
|
||
| Date | Version | Changes |
|
||
|------|---------|---------|
|
||
| 2026-02-10 | 1.1 | Integrated mailcow native backup_and_restore.sh script as primary backup method |
|
||
| 2026-02-10 | 1.0 | Initial documentation |
|
||
|
||
---
|
||
|
||
**Last Updated**: February 10, 2026
|
||
**Maintained By**: System Administrator
|
||
**Review Schedule**: Quarterly
|