
Your backups aren’t “done” when they finish uploading. They’re done when you can restore them, boot the result, and confirm the site and mail flow behave normally. This VPS backup verification tutorial shows a practical way to prove restore readiness on Linux hosting servers in 2026—without turning backup management into a second job.
The workflow fits a typical HostMyCode VPS running WordPress, small business sites, or reseller workloads. It uses file backups + database dumps + config snapshots, then runs a scheduled restore test in an isolated sandbox.
On a busier node, the same pattern scales to a dedicated server with larger datasets and tighter RPO/RTO targets.
What you’ll build in this VPS backup verification tutorial
- A repeatable backup verification checklist (files, databases, configs, and boot-level sanity).
- Automated integrity checks (hashing, repository checks, and “can we list backups?” validations).
- A weekly restore test that mounts or restores data into an isolated verification environment.
- A short “restore-first” runbook you can follow at 3 AM.
Prerequisites and a safe verification environment
You need two Linux systems:
- Source VPS: your production server (Ubuntu 24.04 LTS or Debian 12 are common in 2026).
- Verification target: a small sandbox VPS that can pull backups and run restore tests without touching production.
If you don’t have a spare server yet, start with an inexpensive HostMyCode VPS as the verification box. Keep it in the same region for faster restores.
Then add an offsite copy for true disaster scenarios.
Security rule: the verification target should have read-only access to backup repositories. It should never be able to delete or prune your only copy.
Pick a backup type (and what you can realistically verify)
- File-level backups (rsync/restic/borg): easiest to verify quickly; you can hash, list, and restore into a temp directory.
- Image-level backups (snapshots/vzdump): best for full-system recovery; verification often means restoring to a new VM and booting it.
- Control-panel backups (cPanel/DirectAdmin): verify by restoring a test account and checking permissions, DNS zones, and mailboxes.
This tutorial focuses on file-level backups (common on VPS hosting). It also adds a lightweight boot-level sanity option.
Step 1: Define what “restore success” means (RPO/RTO, scope, and exclusions)
Before you automate anything, write down what your backups must deliver:
- RPO (how much data you can lose): e.g., 24 hours for brochure sites, 1 hour for WooCommerce orders.
- RTO (how fast you must recover): e.g., 2 hours to get a basic site online.
- Scope: web roots, uploads, mail spools, crons, TLS certs, and key configs.
- Exclusions: caches, tmp, node_modules, large logs (verify they’re excluded on purpose).
If you don’t already have a restore procedure, keep it brief and executable. HostMyCode customers often use a runbook approach.
The structure in our Disaster Recovery Tutorial (2026) maps cleanly to a “verify, then recover” workflow.
Step 2: Inventory your backups (what exists, where, and how to access it)
Create a single inventory file on the source VPS. It prevents the “we backed up something… probably” problem.
# /root/backup-inventory.txt
# Web
/var/www
/etc/nginx
/etc/apache2
/etc/letsencrypt
# Databases (logical dumps or raw files depending on your stack)
/var/backups
# Mail (if you host mail locally)
/var/mail
/var/spool/postfix
/etc/postfix
/etc/dovecot
# System identity/config
/etc
/root
On cPanel servers, add /home, /var/cpanel, and the account backups location. If you manage cPanel, keep your restore workflow close.
This pairs well with cPanel Account Restore Tutorial (2026).
Step 3: Add integrity checks you can run daily
Verification isn’t one big test. Start with cheap checks that catch the usual failures.
Common issues include: repository unreachable, no “latest” backup, empty artifacts, or dumps you can’t read.
3.1 Check the latest backup exists and looks sane
Assume you store backups under /backups on a remote host or mounted storage. Adjust paths for your setup.
# Example: latest backup tarballs or directories
ls -lah /backups | tail -n 20
Watch for backups that suddenly shrink to near-zero. That often points to permissions, a broken include path, or an exclusion rule that’s now too broad.
3.2 Hash a small, stable sample (fast signal without hashing everything)
Hashing multi-GB backups every day adds real overhead. Instead, hash a small set of stable files (core config). Use it as a drift signal.
# /usr/local/sbin/backup-sample-hash.sh
#!/bin/bash
set -euo pipefail
SAMPLE_FILES=(
"/etc/hosts"
"/etc/fstab"
"/etc/nginx/nginx.conf"
)
OUT_DIR="/var/log/backup-verify"
mkdir -p "$OUT_DIR"
STAMP=$(date -u +%F)
OUT="$OUT_DIR/sample-hashes-$STAMP.txt"
for f in "${SAMPLE_FILES[@]}"; do
if [[ -f "$f" ]]; then
sha256sum "$f" >> "$OUT"
fi
done
echo "Wrote $OUT"
This doesn’t prove the backup copy is good. It gives you a baseline that helps during incident response.
It’s especially useful when you need to spot unexpected config changes.
3.3 Validate database dumps can actually be read
If you run nightly dumps into /var/backups, add a “can we parse it?” check. For MySQL/MariaDB dumps:
# Replace with your dump path
DUMP=/var/backups/mysql/latest.sql.gz
gzip -t "$DUMP" # verifies gzip integrity
zcat "$DUMP" | head -n 50
For PostgreSQL custom format dumps, verify with:
pg_restore -l /var/backups/postgres/latest.dump | head
If you host both WordPress and email on the same box, keep mail checks as their own item. Restores often change hostnames, rDNS, and TLS names.
Those changes can break mail in confusing ways. If mail matters to you, keep Mail Queue Troubleshooting Tutorial (2026) nearby for the day a “successful restore” still can’t deliver messages.
Step 4: Run a weekly restore test to a verification VPS
Daily integrity checks tell you the job is still running. Weekly restore tests tell you the result is usable.
There are two common approaches:
- Restore-in-place to a temp directory on the verification VPS (fast, safe, doesn’t require booting).
- Full system restore to a new VM or new disk (best signal, heavier operational cost).
A practical middle ground works well for many stacks. Restore site files, import a database into a local test schema, then run a small set of checks that fail loudly.
4.1 Provision the verification VPS (minimal packages)
sudo apt update
sudo apt -y install rsync tar gzip curl jq mysql-client coreutils
If your backups move over SSH/SFTP, treat that path like an admin interface. A weak transport setup becomes a predictable target.
For hardened patterns, see SFTP Setup Guide Tutorial (2026).
4.2 Pull the latest backup to the verification VPS (read-only key)
Create a dedicated SSH key on the verification VPS. Restrict it on the backup storage side (command=, from=, and no-pty if possible).
# On verification VPS
sudo -i
ssh-keygen -t ed25519 -f /root/.ssh/backup_ro_key -N ""
# Example rsync pull (adjust host/path)
rsync -aH --numeric-ids -e "ssh -i /root/.ssh/backup_ro_key" \
backupuser@backup-storage.example:/backups/latest/ /srv/restore-test/latest/
If you don’t want your backup storage reachable from the public internet, use a jump host. The pattern in SSH Jump Host Setup Guide Tutorial (2026) applies cleanly to backup networks.
4.3 Restore to a temp path and validate ownership and structure
After pulling the backup, restore it into a clean directory. Example for a tarball backup:
mkdir -p /srv/restore-test/unpacked
tar -xzf /srv/restore-test/latest/site-files.tar.gz -C /srv/restore-test/unpacked
# Quick sanity: expected paths exist
test -d /srv/restore-test/unpacked/var/www || echo "Missing /var/www"
# Ownership check (common failure: everything becomes root:root)
find /srv/restore-test/unpacked/var/www -maxdepth 2 -type d -printf '%u:%g %p\n' | head -n 30
If ownership is wrong across the board, treat that as a failed restore test. Your process isn’t preserving IDs.
Fix it now, while the impact is low.
4.4 Import the database into a disposable test schema
Create a throwaway database and import the dump. This catches truncated dumps, missing routines, privilege issues, and character set problems.
# Example for MySQL/MariaDB
mysql -h 127.0.0.1 -u root -p -e "CREATE DATABASE restore_verify;"
zcat /srv/restore-test/latest/db.sql.gz | mysql -h 127.0.0.1 -u root -p restore_verify
# Confirm tables exist
mysql -h 127.0.0.1 -u root -p -e "SHOW TABLES" restore_verify | head
If you’d rather not run a DB server on the verification VPS, you can still validate dumps by parsing and checking sizes. Importing is stronger evidence, though.
It also catches more real-world failure modes.
4.5 Run basic application-level checks (HTTP + assets + redirects)
You don’t need a perfect production clone to verify backups. You need a few checks that quickly tell you “this restore is broken.”
- Check that wp-content/uploads exists and has files.
- Check for missing media by grepping a few URLs from the DB.
- Check config files exist (wp-config.php, vhost conf, TLS cert paths).
# Example WordPress structure checks
DOCROOT=/srv/restore-test/unpacked/var/www/example.com/public_html
test -f "$DOCROOT/wp-config.php" || echo "Missing wp-config.php"
test -d "$DOCROOT/wp-content/uploads" || echo "Missing uploads"
find "$DOCROOT/wp-content/uploads" -type f | head -n 10
If your restore plan includes TLS, treat renewal as its own dependency after a rebuild. Incidents often fail here: wrong A record, wrong webroot, or a blocked HTTP-01 challenge.
Keep SSL Renewal Troubleshooting Tutorial (2026) handy so you can move from symptom to fix quickly.
Step 5: Automate the verification as a systemd timer (not cron)
systemd timers give you cleaner logging and a more predictable runtime environment. Keep the design simple.
Run one script weekly, exit non-zero on failure, and make results easy to review.
5.1 Create the verification script
# /usr/local/sbin/weekly-restore-verify.sh
#!/bin/bash
set -euo pipefail
BACKUP_HOST="backup-storage.example"
BACKUP_USER="backupuser"
BACKUP_PATH="/backups/latest/"
DEST="/srv/restore-test/latest"
KEY="/root/.ssh/backup_ro_key"
LOGDIR="/var/log/backup-verify"
mkdir -p "$LOGDIR"
LOG="$LOGDIR/weekly-restore-verify-$(date -u +%F).log"
exec > >(tee -a "$LOG") 2>&1
echo "[+] Starting weekly restore verification: $(date -u)"
rm -rf "$DEST"
mkdir -p "$DEST"
# 1) Pull backup
rsync -aH --numeric-ids -e "ssh -i $KEY" \
"$BACKUP_USER@$BACKUP_HOST:$BACKUP_PATH" "$DEST/"
echo "[+] Pulled backup content"
# 2) Check expected artifacts
test -f "$DEST/site-files.tar.gz" || { echo "Missing site-files.tar.gz"; exit 2; }
test -f "$DEST/db.sql.gz" || { echo "Missing db.sql.gz"; exit 2; }
# 3) Integrity of gzip
gzip -t "$DEST/site-files.tar.gz"
gzip -t "$DEST/db.sql.gz"
echo "[+] gzip integrity OK"
# 4) Unpack to temp
UNPACK="/srv/restore-test/unpacked"
rm -rf "$UNPACK"
mkdir -p "$UNPACK"
tar -xzf "$DEST/site-files.tar.gz" -C "$UNPACK"
echo "[+] Unpacked files"
# 5) Minimal structure check
if [[ ! -d "$UNPACK/var/www" ]]; then
echo "Missing /var/www in archive layout"
exit 3
fi
echo "[+] Structure OK"
echo "[+] Verification complete: $(date -u)"
Make it executable:
sudo chmod 750 /usr/local/sbin/weekly-restore-verify.sh
5.2 Add a systemd service and timer
# /etc/systemd/system/weekly-restore-verify.service
[Unit]
Description=Weekly backup restore verification
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/weekly-restore-verify.sh
# /etc/systemd/system/weekly-restore-verify.timer
[Unit]
Description=Run weekly backup restore verification
[Timer]
OnCalendar=Sun *-*-* 03:15:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now weekly-restore-verify.timer
sudo systemctl list-timers | grep restore-verify
If your script relies on routes that can break (VPN, firewall rules, changed ports), add a quick preflight. Check DNS resolution, TCP reachability, and an SSH handshake.
If the connection fails, you may be looking at a network change—not corrupted backups. The flow in Firewall troubleshooting tutorial (2026) helps separate “can’t reach storage” from “restore artifacts are bad.”
Step 6: Add alerting so failures don’t sit quietly
Verification nobody sees is just a log file. Pick at least one:
- Send an email on failure to an external mailbox (not hosted on the same VPS).
- Push a webhook to your chat system.
- Monitor the systemd unit status via your monitoring stack.
Email-on-failure from the verification VPS can work. Don’t bet your alerting on the same mail system you’re trying to recover.
If your mail stack is touchy, use a dedicated relay. Many HostMyCode customers route critical alerts through a clean SMTP relay on a small VPS for exactly that reason.
Step 7: Write the restore runbook (short, specific, and printable)
Your runbook shouldn’t read like documentation. It should read like a checklist you can follow while you’re tired and getting pinged.
Restore runbook template (copy/paste)
- Declare impact: which domains/services are down, when it started, current status page note.
- Freeze changes: pause deployments, disable auto-updates if they’re causing churn.
- Pick restore point: last known good backup timestamp.
- Provision replacement: new VPS or rebuild existing; record IP, hostname, OS version.
- Restore order: base OS → users/permissions → web files → database → configs → TLS → DNS cutover.
- Validation: homepage 200, login works, media loads, cron runs, mail send/receive (if applicable).
- DNS cutover: lower TTL, change A/AAAA, confirm propagation, keep rollback plan.
- Post-restore: rotate secrets if compromise suspected, review logs, document root cause.
If DNS cutovers are part of your recovery, test TTL handling and rollback ahead of time. The steps in DNS Cutover Tutorial (2026) are a good “do this, then this” reference for the day you need to move fast.
Common restore verification failures (and what they usually mean)
- Backups exist but are tiny: path exclusions or permission errors; the job ran but didn’t read data.
- Archive restores as root:root: ownership not preserved; fix tar/rsync options and test again.
- DB dump imports fail: truncated dumps, wrong character set, missing privileges, or wrong dump method.
- WordPress works but media is missing: uploads directory excluded, or object storage not accounted for.
- TLS fails after restore: certs not restored, or Let’s Encrypt challenge path/ports differ on the rebuilt host.
Practical checklist: your weekly backup verification routine
- Verify you can list the latest backup on remote storage.
- Verify integrity (gzip -t, repository check, or checksum for the archive).
- Restore to a clean directory on a verification VPS.
- Confirm expected paths, file count, and ownership look normal.
- Validate database dumps by importing to a disposable database (preferred).
- Record result: pass/fail, timestamp, and any drift or missing scope.
If you want restore testing that’s predictable—no noisy neighbors, no surprise disk pressure—run production sites on a managed VPS hosting plan and keep a smaller verification box on a standard HostMyCode VPS. That two-node setup makes weekly restore tests routine instead of wishful thinking.
FAQ
How often should I run a restore test?
Weekly is a solid baseline for most VPS hosting. If you take orders or bookings all day, test more often and shorten your RPO with more frequent backups.
Do I need a separate verification VPS?
You can restore into a temp directory on the same server, but you’ll miss key failure modes (wrong keys, unreachable storage, missing dependencies). A separate verification VPS is a low-cost way to make the test realistic.
What’s the minimum I should verify for WordPress?
Restore wp-content (especially uploads), import the database, and confirm wp-config.php and your vhost config exist. If you can’t import the DB dump, you don’t have a usable backup.
Should I verify full server images too?
If you rely on image-level backups, yes. The strongest test is restoring to a new VM and confirming it boots and serves traffic on an internal IP before any DNS change.
Summary: make restores boring on purpose
Backup verification isn’t optional work. It’s what turns “we have backups” into “we can recover.” Start with daily integrity checks, add a weekly restore test on a verification VPS, and keep a short runbook that matches how you actually rebuild servers.
If you’re rolling this out across many sites or client accounts, a dedicated server for storage and verification jobs can keep production nodes cleaner. For smaller setups, a pair of HostMyCode VPS instances (production + verification) is enough to keep restores proven in 2026.