10 KiB
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:
- Reading 1.4TB live data while the system is running
- Compressing with zstd (CPU-intensive) at same time
- Writing compressed data to disk (more I/O)
- All three happening simultaneously = I/O bottleneck
- Load average jumped to 21.89 (system completely frozen)
- Apache couldn't respond to requests (504 errors)
- 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:
- Create a frozen copy (snapshot) of
/mnt/nextcloud-data - Backup the snapshot while original stays live
- 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:
#!/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
chmod +x /usr/local/bin/nextcloud-backup.sh
Step 3: Create Systemd Service + Timer
Create /etc/systemd/system/nextcloud-backup.service:
[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:
[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
# 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):
{
"uploads": {
"maxParallelFileWrites": 2,
"maxParallelSmallFileWrites": 2,
"ignoreFileErrors": false
},
"cache": {
"metadata": {
"maxCacheSize": 500000000
}
},
"logging": {
"level": "info"
}
}
Run Kopia with Nice/Ionice
# Instead of running kopia directly
nice -n 15 ionice -c3 docker exec kopia kopia snapshot create
Or in crontab:
# 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
# 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
# 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:
# 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:
SNAPSHOT_SIZE="500G" # Instead of 200G
"Backup file is too large"
Disable compression to see raw size:
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
# 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:
- ✅ Zero impact on live system during backup
- ✅ Consistent point-in-time copy
- ✅ Safe to interrupt without data loss
- ✅ Easy to automate with systemd
- ✅ Works with any backup tool (tar, Kopia, rsync)
Next steps:
- SSH to nextcloud VM
- Copy backup script from above
- Create systemd service + timer
- Enable and test
- Monitor first backup run
- Adjust schedule based on timing
You'll have reliable, non-disruptive backups running every night at 2 AM with zero system impact! 🎯