
A backup you haven’t restored is just a wish. This VPS backup setup guide tutorial walks through a production-friendly routine for Ubuntu. You’ll set up fast local snapshots, encrypted offsite copies, and a repeatable restore test that proves you can recover.
The goal is simple. If your VPS gets popped, a disk fails, or an update breaks the stack, you can bring the site back quickly. You’ll also know exactly what you’re rolling back to.
What you’ll build in this VPS backup setup guide tutorial
- Layer 1: Local snapshots for quick rollbacks (minutes).
- Layer 2: Offsite backups for real disasters (datacenter loss, ransomware).
- Layer 3: Restore testing so you catch missing files, bad permissions, and broken configs before an outage does.
This tutorial assumes Ubuntu Server 24.04 LTS or Ubuntu 26.04 LTS on a typical hosting VPS. Think Nginx or Apache, PHP, and WordPress (optional).
The same pattern works on dedicated servers. You’ll just have more storage and more components to protect.
Prerequisites (keep it boring and predictable)
- A VPS running Ubuntu with root or sudo access.
- Enough local disk to keep at least 2 snapshot points (often 10–30% overhead).
- An offsite destination: another VPS, S3-compatible object storage, or a backup server reachable over SSH.
- A maintenance window to run your first full backup and a restore test.
If you’re still tightening basic access controls, start with this VPS hardening tutorial and SSH lockdown first.
Backups don’t help much if an attacker can log in and wipe them.
Need a clean server to implement this end-to-end? A HostMyCode VPS is a solid base for automation. managed VPS hosting also works well if you want someone else handling daily maintenance.
Step 1: Decide what to back up (and what to exclude)
On most web VPS setups, the “must-have” list is smaller than people think. Focus on data you can’t recreate and configuration you don’t want to rebuild under pressure.
Include these paths on a typical web VPS
/var/www/(site code + uploads)/etc/(Nginx/Apache, PHP-FPM pools, cron, SSL settings, system configs)/home/(if you host multiple users or use SFTP users)/var/lib/for app state where relevant (be selective)/srv/if you keep sites there/var/log/is optional; keep short history for forensics, but don’t bloat backups
Exclude these to avoid huge, noisy backups
/proc,/sys,/dev,/run- Package caches:
/var/cache/apt/archives - Container layers if you use Docker heavily (back up your compose files and volumes instead)
- Large temporary folders:
/tmp, application temp directories
If you run WordPress
For WordPress, the “business data” usually comes down to three things:
wp-content/(uploads, themes, plugins)- Your database (covered below)
wp-config.phpand web server vhost configs
A staging workflow reduces risk before updates. If you don’t have one, use this WordPress staging tutorial to test changes before they hit production.
Step 2: Add fast local snapshots (quick rollback layer)
Snapshots aren’t your whole backup strategy. They’re your fastest “undo” button when a deployment, package upgrade, or config change goes wrong.
Many Ubuntu VPS images use LVM, so we’ll use LVM snapshots here. ZFS/Btrfs snapshots also work if that’s what you run.
2.1 Check if you’re on LVM
lsblk
sudo vgs
sudo lvs
If you see volume groups and logical volumes (for example ubuntu-vg and ubuntu-lv), you’re good to snapshot.
2.2 Create an LVM snapshot before risky changes
Pick a snapshot size based on write activity. For many web servers, 5G to 20G is a reasonable starting point.
# Example names; adapt to your VG/LV
sudo lvcreate -L 10G -s -n root-preupdate /dev/ubuntu-vg/ubuntu-lv
sudo lvs
Important: LVM snapshots grow as the origin volume changes. If the snapshot fills, it becomes invalid.
Keep snapshots short-lived (hours to days). Remove them once you’ve verified the change.
2.3 Remove snapshots after validation
sudo lvremove /dev/ubuntu-vg/root-preupdate
Snapshot pitfalls to avoid
- Don’t keep a snapshot for weeks on a busy WordPress site—it will fill.
- Snapshots are local. If the disk dies, they die too.
- Snapshots don’t replace offsite backups. They mainly reduce downtime for simple rollbacks.
Step 3: Create an encrypted offsite backup with Restic (the real safety net)
Offsite backups cover the problems snapshots can’t. This includes accidental deletion, server compromise, filesystem corruption, and complete node loss.
In 2026, Restic is still a practical fit for VPS backups. You get deduplication and encryption by default. You also get automation that stays maintainable.
3.1 Install Restic
sudo apt update
sudo apt install -y restic
3.2 Choose an offsite target (SSH repo option)
A simple, reliable pattern is backing up over SSH to a second machine (a dedicated “backup VPS”). Host it on separate infrastructure if you can.
If you want a clean split from production, consider a second HostMyCode VPS dedicated to backups and monitoring.
On the backup server:
sudo adduser --disabled-password --gecos "" restic
sudo mkdir -p /backups/restic
sudo chown -R restic:restic /backups/restic
On the production VPS, create a dedicated SSH key:
sudo mkdir -p /root/.ssh
sudo ssh-keygen -t ed25519 -a 100 -f /root/.ssh/restic_ed25519 -C "restic-backup"
Copy the public key to the backup server (use the restic user):
sudo ssh-copy-id -i /root/.ssh/restic_ed25519.pub restic@BACKUP_SERVER_IP
Lock the key down on the backup server. Restrict it to Restic only.
Edit:
sudo nano /home/restic/.ssh/authorized_keys
Prefix the key with:
command="restic serve",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAA...
3.3 Initialize the Restic repository
On the production VPS:
export RESTIC_REPOSITORY="sftp:restic@BACKUP_SERVER_IP:/backups/restic"
export RESTIC_PASSWORD="use-a-long-random-password"
export RESTIC_SSH_COMMAND="ssh -i /root/.ssh/restic_ed25519"
restic init
Keep RESTIC_PASSWORD in a password manager. If you lose it, the backups are effectively gone.
3.4 Back up web data + configs (first run)
restic backup \
/etc \
/var/www \
/home \
--exclude /var/www/*/cache \
--exclude /var/www/*/wp-content/cache \
--exclude /var/www/*/wp-content/wflogs
The first run is the slow one. After that, Restic only ships deltas.
3.5 Add a database dump (without turning backups into a mess)
Even on “simple” WordPress installs, the database is often the only truly irreplaceable part.
A clean approach is to dump into a temporary directory, back up the dump, then delete older dumps locally.
Create a dump directory:
sudo mkdir -p /var/backups/db
sudo chmod 700 /var/backups/db
MariaDB/MySQL example (single site DB):
DB_NAME="wordpress"
DB_USER="wp_user"
mysqldump --single-transaction --quick --routines --triggers \
-u "$DB_USER" -p "$DB_NAME" | gzip > /var/backups/db/${DB_NAME}-$(date +%F).sql.gz
Then back up the dump file and remove older local dumps:
restic backup /var/backups/db
find /var/backups/db -type f -name "*.sql.gz" -mtime +2 -delete
If you need a more complete email workflow, DNS checks, and reputation basics, keep this deliverability troubleshooting tutorial handy.
Email deliverability is a common “post-restore” surprise when IPs and DNS change.
Step 4: Automate backups with systemd timers (cleaner than cron)
Cron works, but systemd timers are easier to audit. You get consistent logging and clear status output. You also avoid “where did this job run from?” mysteries.
You’ll create a script, a service unit, and a timer.
4.1 Create the backup script
Create /usr/local/sbin/restic-backup.sh:
sudo nano /usr/local/sbin/restic-backup.sh
Paste (adjust repository, exclusions, and DB settings):
#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY="sftp:restic@BACKUP_SERVER_IP:/backups/restic"
export RESTIC_PASSWORD="REPLACE_WITH_LONG_RANDOM_PASSWORD"
export RESTIC_SSH_COMMAND="ssh -i /root/.ssh/restic_ed25519"
# Optional: dump a DB before file backup
DUMP_DIR="/var/backups/db"
mkdir -p "$DUMP_DIR"
chmod 700 "$DUMP_DIR"
DB_NAME="wordpress"
DB_USER="wp_user"
# Use a protected credentials file instead of putting DB passwords in the script.
# Example: /root/.my.cnf with 600 perms.
mysqldump --single-transaction --quick --routines --triggers \
-u "$DB_USER" "$DB_NAME" | gzip > "$DUMP_DIR/${DB_NAME}-$(date +%F).sql.gz"
# Run backup
restic backup /etc /var/www /home "$DUMP_DIR" \
--exclude /var/www/*/cache \
--exclude /var/www/*/wp-content/cache \
--exclude /var/www/*/wp-content/wflogs
# Retention policy (tune to your needs)
restic forget --prune --keep-daily 7 --keep-weekly 4 --keep-monthly 6
# Cleanup local dumps
find "$DUMP_DIR" -type f -name "*.sql.gz" -mtime +2 -delete
Make it executable:
sudo chmod 700 /usr/local/sbin/restic-backup.sh
Tip: Put MySQL credentials in /root/.my.cnf (permissions 600). This avoids hardcoding passwords:
sudo nano /root/.my.cnf
sudo chmod 600 /root/.my.cnf
[client]
user=wp_user
password=YOUR_DB_PASSWORD
4.2 Create the systemd service
sudo nano /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic backup job
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/restic-backup.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
4.3 Create the timer (daily)
sudo nano /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run Restic backup daily
[Timer]
OnCalendar=*-*-* 02:15:00
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl list-timers --all | grep restic
4.4 Check logs and job status
sudo systemctl status restic-backup.timer
sudo journalctl -u restic-backup.service --since "today"
Step 5: Add a weekly restore test (this is where most setups fail)
Backups fail quietly. Permissions drift. A plugin starts writing giant cache directories.
Your exclude list can also catch files you actually need. A restore test surfaces that drift before you’re restoring during an outage.
5.1 Verify repository integrity
Run weekly (monthly at minimum):
export RESTIC_REPOSITORY="sftp:restic@BACKUP_SERVER_IP:/backups/restic"
export RESTIC_PASSWORD="..."
export RESTIC_SSH_COMMAND="ssh -i /root/.ssh/restic_ed25519"
restic check
5.2 Do a file-level restore into a temp directory
sudo mkdir -p /root/restore-test
sudo chmod 700 /root/restore-test
restic snapshots
# Pick the latest snapshot ID
restic restore latest --target /root/restore-test
Then check a few items that matter:
- Your site files exist:
/root/restore-test/var/www/... - Your web server configs exist:
/root/restore-test/etc/nginx/or/root/restore-test/etc/apache2/ - Your database dump exists and is non-empty:
ls -lh /root/restore-test/var/backups/db/
5.3 Test a database restore safely
Create a temporary database on a test server. You can also do it on the same VPS if you’re careful.
Example on the same machine:
mysql -u root -p -e "CREATE DATABASE restore_test;"
Restore from the latest dump:
zcat /root/restore-test/var/backups/db/wordpress-$(date +%F).sql.gz | mysql -u root -p restore_test
If the dump file name doesn’t match today’s date (common), pick the newest dump:
ls -1t /root/restore-test/var/backups/db/*.sql.gz | head -n 1
5.4 Clean up restore-test data
sudo rm -rf /root/restore-test
mysql -u root -p -e "DROP DATABASE restore_test;"
Step 6: Document your recovery plan (you’ll thank yourself later)
Write down the exact restore steps for your environment. Keep it short and specific.
Store it somewhere off the VPS (a password manager secure note is a good place).
Minimum viable recovery runbook
- Where your Restic repo lives (hostname/IP, path).
- Where the Restic password is stored.
- SSH key location for backup access.
- How to restore files (
restic restoreexample you’ve tested). - How to restore DB (commands, credentials source).
- DNS steps: TTL strategy and where to change records.
If your recovery plan includes moving to a new server, pair this with this near-zero downtime migration guide.
Backups help during migrations, but they aren’t the whole story. Plan for DNS timing, SSL re-issuance, and verification steps.
Step 7: Hardening the backup path (small changes, big payoff)
Your backups contain the same secrets and customer data your production server does. Treat the backup path like a first-class production system.
Checklist: secure backup operations
- Use a dedicated SSH key for backups only (done above).
- Restrict the key with
command="restic serve"on the backup server. - Limit inbound SSH on the backup server to your production VPS IP.
- Patch both machines and reboot for kernel updates in your maintenance cadence.
- Firewall rules: allow only ports you need (usually 22 on the backup server, and 22/80/443 on the web server).
If you want a clean hosting ruleset, use this UFW firewall tutorial or, if you prefer explicit rules, this IPTables ruleset guide.
Step 8: Operational tuning: retention, bandwidth, and backup windows
Two issues show up over and over: retention that’s too short, and backups that run during peak traffic.
Both are easy to fix once you spot them.
Practical retention defaults for hosting
- Daily: 7–14 snapshots
- Weekly: 4–8 snapshots
- Monthly: 6–12 snapshots
For WooCommerce or sites with frequent updates, keep more dailies. Storage is usually cheaper than losing a week of orders or content.
Keep backups from hurting site performance
- Run backups off-peak (systemd timer at night).
- Use
Niceand I/O scheduling (already set in the service unit). - Exclude caches and logs that churn.
- If your VPS is tight on CPU/RAM, consider upgrading plans rather than trying to tune around hard limits.
If you’re already operating close to the edge, upgrading to managed VPS hosting can be practical.
You get help with maintenance windows, performance checks, and recovery planning—not just compute.
Step 9: Quick diagnostics (when backups fail at 2 AM)
Restic can’t connect over SSH
- Test the SSH command Restic will use:
ssh -i /root/.ssh/restic_ed25519 restic@BACKUP_SERVER_IP
- Confirm the backup server firewall allows SSH from your VPS IP only.
- Check
/home/restic/.ssh/authorized_keysfor the forced command syntax.
Timer runs but no backups appear
- Inspect logs:
sudo journalctl -u restic-backup.service -n 200 --no-pager
- Run the script manually to see interactive errors:
sudo /usr/local/sbin/restic-backup.sh
Backup size exploded overnight
- List largest directories in your web root:
sudo du -xhd1 /var/www | sort -h
- Common offenders: WordPress security plugin logs, caching directories, old staging copies, large media uploads.
Summary: a backup routine you can actually trust
You now have a layered setup: fast local snapshots for rollbacks, encrypted offsite backups for disasters, and a restore test that proves recoverability.
That’s the difference between “we take backups” and “we can recover.”
If you want a stable base for this setup, start with a HostMyCode VPS.
If you’d rather hand off routine maintenance while keeping admin control, HostMyCode managed VPS hosting fits how most production sites run in 2026.
If you’re building a backup plan for a production website, start with infrastructure you can rely on. HostMyCode offers VPS hosting that’s straightforward to administer, plus managed VPS hosting when you want help with patching, monitoring, and recovery planning.
FAQ
How often should I back up a WordPress VPS in 2026?
Daily is the baseline for most sites. If you publish frequently or run WooCommerce, add more frequent database dumps (every 6–12 hours) and keep at least 7 daily restore points.
Are VPS provider snapshots enough?
They help, but they’re not enough by themselves. Provider snapshots are usually stored in the same provider environment. Keep at least one offsite copy you control, encrypted, with a tested restore process.
What’s the biggest backup mistake on hosting servers?
Not testing restores. The second biggest is backing up everything (caches, logs, temp files) until backups become slow and unreliable.
Should I back up SSL certificates and private keys?
Yes. Back up /etc/letsencrypt/ (or your certificate directory) as part of /etc. If you rotate servers, having the private key avoids surprises with older clients and simplifies recovery.
How do I know my backup retention is “enough”?
Match retention to your risk window. If you might not notice a compromise for a week, keep at least 14 daily backups. If clients ask for content restored “from last month,” keep monthly points for 6–12 months.