Most server failures are survivable — if you have recent backups. Most data loss events are catastrophic — if you don’t. A Hong Kong VPS running production workloads needs a layered backup strategy that covers both rapid recovery from common failures (accidental deletion, configuration errors) and disaster recovery from severe events (hardware failure, ransomware, data centre incident). This guide designs a complete, automated backup system.
The 3-2-1 Backup Rule
The industry standard backup framework:
- 3 copies of your data
- 2 different storage media/locations
- 1 copy off-site (geographically separate from your VPS)
Applied to a Hong Kong VPS: primary data on the VPS NVMe (copy 1), daily snapshots in Server.HK’s storage system (copy 2), and automated off-site backups to Cloudflare R2 or Backblaze B2 (copy 3 — geographically separate).
Layer 1: VPS Snapshots
VPS snapshots capture the entire disk state at a point in time — including OS, configuration, and application files. They enable restoring a server to a known-good state within minutes.
When to Take Snapshots
- Before any significant system change (OS upgrade, major config change, new software install)
- Before applying security patches that might affect application compatibility
- Weekly automated snapshots as a baseline recovery point
<code"># Via Server.HK control panel:
# VPS Management → Snapshots → Take Snapshot
# Name the snapshot descriptively: "pre-php82-upgrade-2026-07-15"
# Via API (if available):
# curl -X POST https://api.server.hk/v1/vps/INSTANCE_ID/snapshot \
# -H "Authorization: Bearer API_TOKEN" \
# -d '{"name": "weekly-backup-2026-07-15"}'Limitations of snapshots: they capture a point-in-time state and are stored within the same infrastructure. They protect against accidental misconfiguration but not data centre-level events. They do not replace off-site backups for databases with frequently changing data.
Layer 2: Automated Database Backups
Databases change constantly — a weekly snapshot misses all data written since the last snapshot. Database-level backups (logical dumps) provide granular, frequent recovery points.
PostgreSQL Automated Backup
<code">cat > /opt/backup/pg-backup.sh << 'EOF' #!/bin/bash set -e DATE=$(date +%Y-%m-%d_%H%M%S) BACKUP_DIR="/opt/backups/postgres" RETENTION_DAYS=7 mkdir -p $BACKUP_DIR # Dump all databases with compression sudo -u postgres pg_dumpall \ --clean \ --if-exists \ | gzip -9 > $BACKUP_DIR/pg_all_$DATE.sql.gz
# Verify the backup is readable
gunzip -t $BACKUP_DIR/pg_all_$DATE.sql.gz || {
echo "BACKUP VERIFICATION FAILED: $DATE" | \
mail -s "PG Backup Error" admin@yourdomain.com
exit 1
}
echo "Backup size: $(du -sh $BACKUP_DIR/pg_all_$DATE.sql.gz | cut -f1)"
# Remove backups older than retention period
find $BACKUP_DIR -name "pg_all_*.sql.gz" -mtime +$RETENTION_DAYS -delete
echo "PostgreSQL backup complete: $DATE"
EOF
chmod +x /opt/backup/pg-backup.shMySQL Automated Backup
<code">cat > /opt/backup/mysql-backup.sh << 'EOF' #!/bin/bash DATE=$(date +%Y-%m-%d_%H%M%S) BACKUP_DIR="/opt/backups/mysql" MYSQL_USER="backup_user" MYSQL_PASS="BackupUserPassword!" mkdir -p $BACKUP_DIR # Dump each database separately (easier to restore individual DBs) mysql -u $MYSQL_USER -p$MYSQL_PASS -e "SHOW DATABASES;" | \ grep -Ev "(Database|information_schema|performance_schema|mysql|sys)" | \ while read DB; do mysqldump \ -u $MYSQL_USER -p$MYSQL_PASS \ --single-transaction \ --quick \ --routines \ --triggers \ $DB | gzip -9 > $BACKUP_DIR/${DB}_$DATE.sql.gz
echo "Backed up: $DB"
done
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
echo "MySQL backup complete: $DATE"
EOF
chmod +x /opt/backup/mysql-backup.sh
# Create read-only MySQL backup user
mysql -u root -p -e "
CREATE USER 'backup_user'@'localhost' IDENTIFIED BY 'BackupUserPassword!';
GRANT SELECT, LOCK TABLES, SHOW VIEW, EVENT, TRIGGER ON *.* TO 'backup_user'@'localhost';
FLUSH PRIVILEGES;"Schedule Database Backups
<code">crontab -e # Add: # PostgreSQL — every 4 hours 0 */4 * * * /opt/backup/pg-backup.sh >> /var/log/pg-backup.log 2>&1 # MySQL — every 4 hours offset by 2 hours 0 2,6,10,14,18,22 * * * /opt/backup/mysql-backup.sh >> /var/log/mysql-backup.log 2>&1
Layer 3: File System Backups
<code">cat > /opt/backup/files-backup.sh << 'EOF' #!/bin/bash DATE=$(date +%Y-%m-%d_%H%M%S) BACKUP_DIR="/opt/backups/files" mkdir -p $BACKUP_DIR # Backup critical directories BACKUP_PATHS=( "/var/www" # Web application files "/etc/nginx" # Nginx configuration "/etc/letsencrypt" # SSL certificates "/opt" # Self-hosted application data "/home" # User home directories ) tar czf $BACKUP_DIR/files_$DATE.tar.gz \ --exclude='/var/www/*/node_modules' \ --exclude='/var/www/*/.git' \ --exclude='/opt/backups' \ "${BACKUP_PATHS[@]}" 2>/dev/null
echo "Files backup size: $(du -sh $BACKUP_DIR/files_$DATE.tar.gz | cut -f1)"
find $BACKUP_DIR -name "files_*.tar.gz" -mtime +3 -delete
EOF
chmod +x /opt/backup/files-backup.sh
# Schedule: daily at 1am
crontab -l | { cat; echo "0 1 * * * /opt/backup/files-backup.sh >> /var/log/files-backup.log 2>&1"; } | crontab -Layer 4: Off-Site Backup to Cloudflare R2
Cloudflare R2 is the ideal off-site backup destination: zero egress fees (no cost to read backups), S3-compatible API, and global availability.
<code"># Install rclone (S3-compatible sync tool) curl https://rclone.org/install.sh | bash # Configure rclone for Cloudflare R2 cat > ~/.config/rclone/rclone.conf << 'EOF' [r2] type = s3 provider = Cloudflare access_key_id = YOUR_R2_ACCESS_KEY secret_access_key = YOUR_R2_SECRET_KEY endpoint = https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com acl = private EOF # Create R2 bucket: hk-vps-backups (via Cloudflare dashboard) # Test connection rclone ls r2:hk-vps-backups
<code">cat > /opt/backup/offsite-sync.sh << 'EOF' #!/bin/bash LOG="/var/log/offsite-backup.log" echo "--- Off-site sync started: $(date) ---" >> $LOG
# Sync all local backups to R2
rclone sync /opt/backups/ r2:hk-vps-backups/ \
--progress \
--log-file=$LOG \
--log-level INFO \
--transfers 4 \
--checksum
# Verify at least one recent backup exists in R2
LATEST=$(rclone ls r2:hk-vps-backups/postgres/ | tail -1)
if [ -z "$LATEST" ]; then
echo "WARNING: No backups found in R2!" | mail -s "Backup Alert" admin@yourdomain.com
fi
echo "--- Off-site sync complete: $(date) ---" >> $LOG
EOF
chmod +x /opt/backup/offsite-sync.sh
# Schedule: sync to R2 after local backups complete
crontab -l | { cat; echo "30 1 * * * /opt/backup/offsite-sync.sh"; } | crontab -Layer 5: Incremental Backup with Restic
For large file collections, rsync copies everything on first run. Restic is a modern backup tool that deduplicates data, encrypts backups, and sends only changed blocks — dramatically reducing backup size and transfer time:
<code">apt install -y restic
# Initialise Restic repository in R2
export AWS_ACCESS_KEY_ID="YOUR_R2_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_R2_SECRET_KEY"
export RESTIC_REPOSITORY="s3:https://YOUR_ACCOUNT.r2.cloudflarestorage.com/hk-vps-restic"
export RESTIC_PASSWORD="STRONG_ENCRYPTION_PASSPHRASE"
restic init
# First backup
restic backup /var/www /etc /opt --exclude /opt/backups
# Subsequent backups (only changed data sent)
restic backup /var/www /etc /opt --exclude /opt/backups
# List snapshots
restic snapshots
# Schedule incremental backups every 6 hours
crontab -l | { cat; echo "0 */6 * * * /usr/bin/restic backup /var/www /etc /opt --exclude /opt/backups >> /var/log/restic.log 2>&1"; } | crontab -
# Auto-prune: keep 7 daily, 4 weekly, 3 monthly snapshots
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 3 --pruneRecovery Testing (Critical — Most Skipped Step)
A backup strategy you have never tested is not a backup strategy — it is an assumption. Test recovery quarterly:
<code"># Test 1: Database restore to a separate test database
gunzip -c /opt/backups/postgres/pg_all_2026-07-15_030000.sql.gz | \
sudo -u postgres psql -d postgres -c "CREATE DATABASE restore_test;"
gunzip -c /opt/backups/postgres/pg_all_2026-07-15_030000.sql.gz | \
sudo -u postgres psql -d restore_test
# Verify row counts
sudo -u postgres psql -d restore_test -c "SELECT COUNT(*) FROM your_main_table;"
# Compare with production
sudo -u postgres psql -d production_db -c "SELECT COUNT(*) FROM your_main_table;"
# Test 2: File restore
tar xzf /opt/backups/files/files_2026-07-15.tar.gz \
-C /tmp/restore-test/ \
var/www/yourdomain/wp-config.php
diff /tmp/restore-test/var/www/yourdomain/wp-config.php \
/var/www/yourdomain/wp-config.php
# Test 3: R2 restore (simulate off-site recovery)
rclone copy r2:hk-vps-backups/postgres/ /tmp/r2-restore-test/
ls -la /tmp/r2-restore-test/
echo "Recovery test complete. Document results and any issues found."Monitoring Backup Health
<code">cat > /opt/backup/verify-backups.sh << 'EOF' #!/bin/bash ERRORS=0 # Check PostgreSQL backup is recent (less than 5 hours old) PG_LATEST=$(find /opt/backups/postgres -name "*.sql.gz" -mmin -300 | wc -l) [ $PG_LATEST -eq 0 ] && { echo "ALERT: No recent PG backup"; ERRORS=$((ERRORS+1)); } # Check backup is non-zero size PG_SIZE=$(find /opt/backups/postgres -name "*.sql.gz" -mmin -300 -size +100k | wc -l) [ $PG_SIZE -eq 0 ] && { echo "ALERT: PG backup too small (may be empty)"; ERRORS=$((ERRORS+1)); } # Check R2 sync (at least one file updated in last 26 hours) R2_RECENT=$(rclone ls r2:hk-vps-backups/ --max-age 26h 2>/dev/null | wc -l)
[ $R2_RECENT -eq 0 ] && { echo "ALERT: R2 sync may be failing"; ERRORS=$((ERRORS+1)); }
if [ $ERRORS -gt 0 ]; then
echo "BACKUP HEALTH CHECK FAILED: $ERRORS issues" | \
mail -s "⚠️ Backup Alert: HK VPS" admin@yourdomain.com
else
echo "Backup health check passed: $(date)"
fi
EOF
chmod +x /opt/backup/verify-backups.sh
# Run health check daily at 8am (after nightly backups complete)
crontab -l | { cat; echo "0 8 * * * /opt/backup/verify-backups.sh"; } | crontab -Conclusion
A complete backup strategy for your Hong Kong VPS combines four layers: VPS snapshots for rapid rollback, automated database dumps every 4 hours for minimal data loss window, daily file system archives, and off-site sync to Cloudflare R2 for geographic redundancy. The strategy is only as strong as your last successful recovery test — schedule quarterly restoration drills to verify backups actually work before you need them under pressure.
Protect your data: Browse Server.HK Hong Kong VPS plans — all plans support the snapshot and backup strategies described in this guide.