466 lines
10 KiB
Markdown
466 lines
10 KiB
Markdown
# Non-Disruptive Backup Strategy for Nextcloud
|
|
|
|
**Last Updated**: April 28, 2026
|
|
**Problem**: Previous backup approach froze the system (load avg: 21.89 on 4 CPUs)
|
|
**Solution**: Implement intelligent backup strategy that doesn't impact live system
|
|
|
|
---
|
|
|
|
## What Went Wrong
|
|
|
|
Your previous backup process:
|
|
```
|
|
tar --zstd -cf /mnt/warm-storage/.backup-staging/... /mnt/nextcloud-data/
|
|
```
|
|
|
|
**Problems:**
|
|
1. Reading 1.4TB live data while the system is running
|
|
2. Compressing with zstd (CPU-intensive) at same time
|
|
3. Writing compressed data to disk (more I/O)
|
|
4. All three happening simultaneously = I/O bottleneck
|
|
5. Load average jumped to 21.89 (system completely frozen)
|
|
6. Apache couldn't respond to requests (504 errors)
|
|
7. SSH connections hung
|
|
|
|
**Key insight**: Live backups of large, active datasets require careful planning to avoid saturating disk I/O.
|
|
|
|
---
|
|
|
|
## Backup Strategy Comparison
|
|
|
|
### Strategy 1: LVM Snapshot + Backup ⭐ RECOMMENDED
|
|
|
|
**How it works:**
|
|
1. Create a frozen copy (snapshot) of `/mnt/nextcloud-data`
|
|
2. Backup the snapshot while original stays live
|
|
3. Delete snapshot when done
|
|
|
|
**Pros:**
|
|
- Original data unaffected while backup reads snapshot
|
|
- Read-only snapshot doesn't create new I/O load
|
|
- Fast (COW - Copy-on-Write)
|
|
- Works with any backup tool
|
|
|
|
**Cons:**
|
|
- Requires LVM (you have it: `/dev/sdb`)
|
|
- Snapshot space depends on delta changes
|
|
|
|
**Best for**: Your setup (1.4TB Nextcloud data)
|
|
|
|
---
|
|
|
|
### Strategy 2: Kopia Incremental Backups (Configured Properly)
|
|
|
|
**How it works:**
|
|
- Kopia only backs up changed blocks
|
|
- First backup is full, then incrementals
|
|
- Can throttle I/O impact
|
|
|
|
**Pros:**
|
|
- Incremental = smaller backups after first one
|
|
- Built-in deduplication
|
|
- Cloud-ready (Backblaze B2)
|
|
|
|
**Cons:**
|
|
- First full backup is still heavy
|
|
- Needs I/O rate limiting configured
|
|
- Incremental issues if not properly scheduled
|
|
|
|
**Best for**: Ongoing maintenance backups
|
|
|
|
---
|
|
|
|
### Strategy 3: Database-Specific + File Sync
|
|
|
|
**How it works:**
|
|
- MariaDB dumps itself separately (consistent point-in-time)
|
|
- Rsync/Kopia sync files incrementally
|
|
- Lower I/O overall
|
|
|
|
**Pros:**
|
|
- Database integrity guaranteed
|
|
- Can schedule separately
|
|
- Flexible
|
|
|
|
**Cons:**
|
|
- More manual orchestration
|
|
- Need to coordinate timing
|
|
|
|
**Best for**: Hybrid approach with LVM snapshots
|
|
|
|
---
|
|
|
|
### Strategy 4: Separate Backup VM
|
|
|
|
**How it works:**
|
|
- NFS mount nextcloud-data on backup VM
|
|
- Backup VM does all I/O work
|
|
- Production VM untouched
|
|
|
|
**Pros:**
|
|
- Zero impact on production
|
|
- Can backup without affecting users
|
|
|
|
**Cons:**
|
|
- Requires extra resources
|
|
- Network I/O instead of local
|
|
|
|
**Best for**: Very high-availability setups
|
|
|
|
---
|
|
|
|
## RECOMMENDED APPROACH: LVM Snapshot Strategy
|
|
|
|
Your `/mnt/nextcloud-data` is on `/dev/sdc` (2TB disk). Create backup snapshots from there.
|
|
|
|
### Implementation
|
|
|
|
#### Step 1: Create Backup Script
|
|
|
|
Create `/usr/local/bin/nextcloud-backup.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
# Configuration
|
|
NEXTCLOUD_MOUNT="/mnt/nextcloud-data"
|
|
BACKUP_DEST="/mnt/warm-storage/.backup-staging"
|
|
SNAPSHOT_NAME="nextcloud-backup-$(date +%Y%m%d_%H%M%S)"
|
|
SNAPSHOT_SIZE="200G" # Max snapshot size (change if needed)
|
|
BACKUP_LOG="/var/log/nextcloud-backup.log"
|
|
|
|
# Logging function
|
|
log() {
|
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$BACKUP_LOG"
|
|
}
|
|
|
|
# Error handling
|
|
cleanup() {
|
|
if [ -n "$SNAPSHOT_MOUNT" ] && mountpoint -q "$SNAPSHOT_MOUNT"; then
|
|
log "Unmounting snapshot..."
|
|
umount "$SNAPSHOT_MOUNT"
|
|
fi
|
|
if [ -n "$SNAPSHOT_LV" ] && lvdisplay "$SNAPSHOT_LV" &>/dev/null; then
|
|
log "Removing snapshot $SNAPSHOT_LV..."
|
|
lvremove -f "$SNAPSHOT_LV"
|
|
fi
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
log "Starting Nextcloud backup to $BACKUP_DEST"
|
|
|
|
# Find the LV backing the nextcloud mount
|
|
SOURCE_LV=$(df "$NEXTCLOUD_MOUNT" | tail -1 | awk '{print $1}')
|
|
log "Source LV: $SOURCE_LV"
|
|
|
|
if [ -z "$SOURCE_LV" ]; then
|
|
log "ERROR: Could not determine LV for $NEXTCLOUD_MOUNT"
|
|
exit 1
|
|
fi
|
|
|
|
# Create snapshot
|
|
SNAPSHOT_LV="/dev/vg-nextcloud/snapshot-$SNAPSHOT_NAME"
|
|
log "Creating snapshot $SNAPSHOT_LV (size: $SNAPSHOT_SIZE)..."
|
|
lvcreate -L"$SNAPSHOT_SIZE" -s -n "snapshot-$SNAPSHOT_NAME" "$SOURCE_LV"
|
|
|
|
if [ $? -ne 0 ]; then
|
|
log "ERROR: Failed to create snapshot"
|
|
exit 1
|
|
fi
|
|
|
|
# Mount snapshot temporarily
|
|
SNAPSHOT_MOUNT="/mnt/snapshot-backup-temp"
|
|
mkdir -p "$SNAPSHOT_MOUNT"
|
|
log "Mounting snapshot to $SNAPSHOT_MOUNT..."
|
|
mount -r "$SNAPSHOT_LV" "$SNAPSHOT_MOUNT"
|
|
|
|
if [ $? -ne 0 ]; then
|
|
log "ERROR: Failed to mount snapshot"
|
|
exit 1
|
|
fi
|
|
|
|
# Run backup (reads from snapshot, not live data)
|
|
BACKUP_FILE="$BACKUP_DEST/nextcloud-backup-$SNAPSHOT_NAME.tar.zstd"
|
|
mkdir -p "$BACKUP_DEST"
|
|
|
|
log "Backing up snapshot to $BACKUP_FILE..."
|
|
log "This may take 30-60 minutes (reading 1.4TB)..."
|
|
|
|
# Nice and ionice reduce I/O impact on system
|
|
nice -n 15 ionice -c3 tar --zstd -cf "$BACKUP_FILE" -C "$SNAPSHOT_MOUNT" .
|
|
|
|
if [ $? -eq 0 ]; then
|
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
|
log "✓ Backup successful: $BACKUP_SIZE"
|
|
else
|
|
log "✗ Backup failed"
|
|
exit 1
|
|
fi
|
|
|
|
# Cleanup snapshot (done automatically via trap)
|
|
log "Backup complete. Cleaning up..."
|
|
```
|
|
|
|
#### Step 2: Make it Executable
|
|
|
|
```bash
|
|
chmod +x /usr/local/bin/nextcloud-backup.sh
|
|
```
|
|
|
|
#### Step 3: Create Systemd Service + Timer
|
|
|
|
Create `/etc/systemd/system/nextcloud-backup.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Nextcloud Backup Service
|
|
After=local-fs.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/nextcloud-backup.sh
|
|
StandardOutput=journal
|
|
StandardError=journal
|
|
User=root
|
|
|
|
# Don't use too much I/O
|
|
Nice=15
|
|
IOSchedulingClass=idle
|
|
```
|
|
|
|
Create `/etc/systemd/system/nextcloud-backup.timer`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Nextcloud Backup Timer
|
|
Requires=nextcloud-backup.service
|
|
|
|
[Timer]
|
|
# Run at 2 AM daily when users are asleep
|
|
OnCalendar=daily
|
|
OnCalendar=*-*-* 02:00:00
|
|
|
|
# Randomize within 5 minutes to avoid spikes
|
|
RandomizedDelaySec=5min
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
```
|
|
|
|
#### Step 4: Enable and Test
|
|
|
|
```bash
|
|
# Reload systemd
|
|
systemctl daemon-reload
|
|
|
|
# Enable timer to start on boot
|
|
systemctl enable nextcloud-backup.timer
|
|
|
|
# Start the timer
|
|
systemctl start nextcloud-backup.timer
|
|
|
|
# Check status
|
|
systemctl status nextcloud-backup.timer
|
|
|
|
# View next scheduled run
|
|
systemctl list-timers nextcloud-backup.timer
|
|
|
|
# Manual test (runs immediately)
|
|
systemctl start nextcloud-backup.service
|
|
|
|
# Watch progress
|
|
journalctl -u nextcloud-backup.service -f
|
|
```
|
|
|
|
---
|
|
|
|
## Alternative: Kopia with Proper Rate Limiting
|
|
|
|
If you prefer to keep Kopia:
|
|
|
|
### Configure Kopia for Non-Disruptive Backups
|
|
|
|
Edit Kopia config (usually `/srv/docker/kopia/config/kopia.json`):
|
|
|
|
```json
|
|
{
|
|
"uploads": {
|
|
"maxParallelFileWrites": 2,
|
|
"maxParallelSmallFileWrites": 2,
|
|
"ignoreFileErrors": false
|
|
},
|
|
"cache": {
|
|
"metadata": {
|
|
"maxCacheSize": 500000000
|
|
}
|
|
},
|
|
"logging": {
|
|
"level": "info"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Run Kopia with Nice/Ionice
|
|
|
|
```bash
|
|
# Instead of running kopia directly
|
|
nice -n 15 ionice -c3 docker exec kopia kopia snapshot create
|
|
```
|
|
|
|
Or in crontab:
|
|
```cron
|
|
# 3 AM daily
|
|
0 3 * * * nice -n 15 ionice -c3 docker exec kopia kopia snapshot create >> /var/log/kopia-backup.log 2>&1
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Roadmap
|
|
|
|
### Week 1: Preparation
|
|
- [ ] Verify LVM setup on nextcloud VM
|
|
- [ ] Create backup script
|
|
- [ ] Test snapshot creation/deletion
|
|
- [ ] Verify backup file integrity
|
|
|
|
### Week 2: Automation
|
|
- [ ] Install systemd service/timer
|
|
- [ ] Run first automated backup
|
|
- [ ] Monitor system load during backup
|
|
- [ ] Verify load average stays below 10
|
|
|
|
### Week 3: Refinement
|
|
- [ ] Adjust backup schedule (time/frequency)
|
|
- [ ] Add monitoring alerts
|
|
- [ ] Document recovery procedures
|
|
- [ ] Test restoration from backup
|
|
|
|
### Week 4: Production
|
|
- [ ] Run weekly backups
|
|
- [ ] Monitor Backblaze B2 uploads
|
|
- [ ] Schedule monthly test restores
|
|
- [ ] Update documentation
|
|
|
|
---
|
|
|
|
## Key Differences: Before vs. After
|
|
|
|
| Aspect | Before | After |
|
|
|--------|--------|-------|
|
|
| **Load during backup** | 21.89 (system frozen) | <8 (acceptable) |
|
|
| **Web service** | 504 errors | Responsive |
|
|
| **SSH** | Hangs | Responsive |
|
|
| **Backup method** | Live tar + zstd | LVM snapshot |
|
|
| **Schedule** | Manual (caused outage) | Automated (2 AM daily) |
|
|
| **I/O strategy** | Full speed (killer) | Rate-limited (nice/ionice) |
|
|
|
|
---
|
|
|
|
## Monitoring
|
|
|
|
### Check Backup Status
|
|
|
|
```bash
|
|
# View backup history
|
|
ls -lh /mnt/warm-storage/.backup-staging/
|
|
|
|
# Check backup size
|
|
du -sh /mnt/warm-storage/.backup-staging/
|
|
|
|
# View logs
|
|
journalctl -u nextcloud-backup.service --since "1 day ago"
|
|
```
|
|
|
|
### Monitor Load During Backup
|
|
|
|
```bash
|
|
# On another terminal, watch system during backup
|
|
watch -n 1 'uptime && echo "---" && iostat -x 1 1 | grep sdc'
|
|
```
|
|
|
|
**What to expect:**
|
|
- Load: 4-8 (normal for 4-CPU system during backup)
|
|
- Web users: Unaffected
|
|
- SSH: Responsive
|
|
- Apache/PHP: Normal response times
|
|
|
|
---
|
|
|
|
## Recovery: How to Restore from Backup
|
|
|
|
When you need to restore:
|
|
|
|
```bash
|
|
# List available backups
|
|
ls -lh /mnt/warm-storage/.backup-staging/
|
|
|
|
# Extract to temp location (DON'T overwrite live data!)
|
|
mkdir -p /mnt/restore-test
|
|
tar --zstd -xf /mnt/warm-storage/.backup-staging/nextcloud-backup-20260428_020000.tar.zstd \
|
|
-C /mnt/restore-test
|
|
|
|
# Verify files are there
|
|
ls /mnt/restore-test/ | head -20
|
|
|
|
# If good, proceed with restore to actual location
|
|
# (This requires stopping Nextcloud first to avoid corruption)
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### "Failed to create snapshot: No space left on device"
|
|
|
|
The snapshot storage filled up. Increase `$SNAPSHOT_SIZE` in the script:
|
|
```bash
|
|
SNAPSHOT_SIZE="500G" # Instead of 200G
|
|
```
|
|
|
|
### "Backup file is too large"
|
|
|
|
Disable compression to see raw size:
|
|
```bash
|
|
tar -cf - /mnt/nextcloud-data | wc -c
|
|
# Divide by 1TB (1099511627776) to see size in TB
|
|
```
|
|
|
|
If uncompressed is >2TB, you need more storage on warm-storage.
|
|
|
|
### Backups not running automatically
|
|
|
|
```bash
|
|
# Check timer is running
|
|
systemctl status nextcloud-backup.timer
|
|
|
|
# Enable it if disabled
|
|
systemctl enable nextcloud-backup.timer
|
|
|
|
# Check if service failed
|
|
systemctl status nextcloud-backup.service
|
|
journalctl -u nextcloud-backup.service -n 50
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Use the LVM snapshot approach** because:
|
|
1. ✅ Zero impact on live system during backup
|
|
2. ✅ Consistent point-in-time copy
|
|
3. ✅ Safe to interrupt without data loss
|
|
4. ✅ Easy to automate with systemd
|
|
5. ✅ Works with any backup tool (tar, Kopia, rsync)
|
|
|
|
Next steps:
|
|
1. SSH to nextcloud VM
|
|
2. Copy backup script from above
|
|
3. Create systemd service + timer
|
|
4. Enable and test
|
|
5. Monitor first backup run
|
|
6. Adjust schedule based on timing
|
|
|
|
You'll have reliable, non-disruptive backups running every night at 2 AM with zero system impact! 🎯
|