
A backup plan only matters if you can answer two questions without guessing: “How much data can I lose?” and “How fast can I recover?” This VPS backup strategy tutorial shows a practical 3-2-1 design for a hosting VPS in 2026. It covers encryption, retention, restore testing, and a simple way to track real RPO/RTO timings.
The examples assume Ubuntu 24.04 LTS or Debian 12 on a VPS running common hosting workloads (WordPress, Nginx/Apache, PHP-FPM, mail). You can use the same structure on AlmaLinux 9/10 or Rocky Linux. Expect small changes in paths and service names.
What you’ll build: a 3-2-1 backup plan you can actually operate
3-2-1 means: 3 copies of your data, on 2 different media, with 1 copy offsite. On a VPS, that usually looks like:
- Copy #1: live data on the VPS
- Copy #2: local backups (fast restores) on attached storage or a second volume
- Copy #3: encrypted offsite backups (survive VPS loss, provider incident, or accidental deletion)
Operationally, set targets you can defend and measure:
- RPO (Recovery Point Objective): the maximum acceptable data loss (example: 1 hour)
- RTO (Recovery Time Objective): the maximum acceptable downtime (example: 30–90 minutes)
If you’re starting from scratch, begin with a predictable server baseline.
A HostMyCode VPS gives you stable resources and root access. That makes automation easier. It also keeps backup jobs out of opaque control-panel workflows you can’t audit.
Prerequisites and safety notes (read this before you run commands)
- You need root or sudo access.
- Plan for backup storage cost: retention is where bills quietly grow.
- Assume your VPS can be compromised. Don’t give it delete rights to your entire offsite history.
- Snapshot-style backups are fast, but application-consistent backups take extra care (databases, mail queues).
Helpful companion reads (worth bookmarking for later):
- VPS restore drill tutorial (2026): prove your backups work
- rsync backup tutorial (2026): incremental backups over SSH
Step 1: Inventory what must be backed up (and what must be excluded)
Before you pick tools, write down what you need to rebuild the server and resume service.
On typical hosting VPS setups, these paths are common:
- Web content: /var/www, /home/*/public_html, or /usr/share/nginx/html
- Web server config: /etc/nginx, /etc/apache2 (or /etc/httpd on RHEL-family)
- PHP config: /etc/php/*/fpm, /etc/php.ini
- Databases: logical dumps or physical files (avoid raw /var/lib/mysql copies unless you know what you’re doing)
- Mail: /var/mail, /var/vmail, /etc/postfix, /etc/exim*, DKIM keys
- SSL keys/certs: /etc/letsencrypt, /etc/ssl/private
- Crontabs & system tasks: /etc/cron*, user crontabs, systemd timers
- Custom app config: /etc, /opt, /srv
Also decide what not to carry into every archive. These are frequent offenders:
- /proc, /sys, /dev (virtual filesystems)
- /tmp and most caches
- Large logs you rotate elsewhere (or compress aggressively)
If you’re not sure what’s growing, scan the top-level directories first:
sudo du -xhd1 / | sort -h
sudo du -xhd1 /var | sort -h
Keeping logs under control makes backups smaller and restores faster.
Reference: Logrotate tutorial (2026): rotate and retain Nginx/Apache/PHP logs.
Step 2: Choose a backup method that matches your restore goals
Most VPS setups work best with two layers:
- Local snapshots for quick rollbacks after updates or configuration mistakes
- File-level encrypted backups for offsite protection and longer retention
Snapshots are fast and convenient. They won’t help if the VPS (or its volume) disappears.
Offsite backups survive that class of failure. They usually take longer to download and rehydrate.
If you want snapshots plus an offsite sync workflow, use this as a reference: Snapshot backup tutorial (2026): LVM/Btrfs rollbacks with offsite sync.
Step 3: Create a dedicated backup user and a safe directory layout
Create a local staging area. Run backup steps under a non-login user.
This won’t prevent every mistake. It does reduce the blast radius when something goes wrong.
sudo adduser --system --group --home /var/backups/backupuser backupuser
sudo mkdir -p /var/backups/staging /var/backups/archives
sudo chown -R backupuser:backupuser /var/backups
sudo chmod 0750 /var/backups
Directory idea:
- /var/backups/staging → temporary dumps (database exports, tar builds)
- /var/backups/archives → final encrypted artifacts ready to ship offsite
Step 4: Add application-aware exports (database + critical configs)
For MySQL/MariaDB on a single-node hosting VPS, logical dumps are the safer default.
Here’s a nightly dump with light compression:
sudo mkdir -p /var/backups/staging/mysql
sudo chmod 0750 /var/backups/staging/mysql
# Create a restricted MySQL user if you can; otherwise use root with a secured defaults file.
# Using mysqldump (works with MariaDB/MySQL):
mysqldump --single-transaction --routines --events --all-databases \
| gzip -1 > /var/backups/staging/mysql/all-databases.sql.gz
For WordPress, capture a small “what was this server running?” manifest. During a restore, these details save time.
sudo mkdir -p /var/backups/staging/manifests
wp --info > /var/backups/staging/manifests/wp-cli.txt 2>/dev/null || true
php -v > /var/backups/staging/manifests/php-version.txt
nginx -v 2> /var/backups/staging/manifests/nginx-version.txt || true
apache2ctl -v > /var/backups/staging/manifests/apache-version.txt 2>/dev/null || true
If WordPress scheduled tasks are flaky, you can restore perfectly and still miss orders or emails.
Keep this nearby: WordPress cron troubleshooting tutorial (2026).
Step 5: Build the backup set with a consistent include/exclude list
Put your scope in files. That keeps it consistent as the server evolves.
sudo tee /etc/backup-include.txt >/dev/null <<'EOF'
/etc
/var/www
/home
/var/vmail
/var/mail
/var/spool/cron
/var/backups/staging
EOF
sudo tee /etc/backup-exclude.txt >/dev/null <<'EOF'
/proc
/sys
/dev
/run
/tmp
/var/tmp
/var/cache
/var/backups/archives
/var/log
EOF
Now build an archive (leave it unencrypted for the moment):
sudo -u backupuser tar -czpf /var/backups/archives/backup.tar.gz \
--absolute-names \
--exclude-from=/etc/backup-exclude.txt \
-T /etc/backup-include.txt
Pitfall: Backing up all of /home on a reseller VPS gets expensive fast.
If you host many accounts, consider per-account archives.
That lets you restore one tenant without pulling everyone else along.
Step 6: Encrypt the backup before it ever leaves the server
Encrypt on the VPS, not after upload.
Keep the passphrase outside the server (password manager, secrets vault, offline). GPG is widely available and easy to audit.
# Create an encrypted file using AES256.
# You will be prompted for a passphrase.
sudo -u backupuser gpg --symmetric --cipher-algo AES256 \
--output /var/backups/archives/backup.tar.gz.gpg \
/var/backups/archives/backup.tar.gz
# Remove the unencrypted artifact after encryption.
sudo -u backupuser shred -u /var/backups/archives/backup.tar.gz
If you need unattended encryption, switch to public-key GPG (recommended) or use a tool like rclone with built-in crypt.
For rclone-based offsite encryption patterns, see: rclone backup tutorial: encrypted offsite backups + restore tests.
Step 7: Ship an offsite copy (SFTP over SSH with restricted access)
Your offsite target can be another VPS, a storage box, or an SFTP endpoint you control.
The non-negotiable piece is access control.
The production VPS should be able to write backups, but not wipe your entire history.
On the offsite server, create a restricted SFTP user and directory:
sudo adduser --disabled-password --gecos "" backuprecv
sudo mkdir -p /srv/backups/vps1
sudo chown backuprecv:backuprecv /srv/backups/vps1
sudo chmod 0750 /srv/backups/vps1
Then lock the user to SFTP (OpenSSH). Edit /etc/ssh/sshd_config on the offsite server:
Match User backuprecv
ChrootDirectory /srv/backups
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
Create the chroot layout (SFTP needs a root-owned chroot):
sudo chown root:root /srv/backups
sudo chmod 0755 /srv/backups
sudo mkdir -p /srv/backups/vps1
sudo chown backuprecv:backuprecv /srv/backups/vps1
sudo systemctl restart ssh
On the production VPS, generate a key and copy it:
sudo -u backupuser ssh-keygen -t ed25519 -f /var/backups/backupuser/.ssh/id_ed25519 -N ""
sudo -u backupuser ssh-copy-id -i /var/backups/backupuser/.ssh/id_ed25519.pub backuprecv@OFFSITE_IP
Now push backups via rsync:
sudo -u backupuser rsync -av --partial --inplace \
/var/backups/archives/backup.tar.gz.gpg \
backuprecv@OFFSITE_IP:/vps1/
If you haven’t locked down SFTP before, use this as a reference: SFTP setup guide tutorial (2026): lock down file transfers on a VPS.
Step 8: Add rotation (daily/weekly/monthly) without fancy tooling
Rotation keeps storage spend predictable. It also makes restore points easy to explain.
One straightforward policy:
- Keep 7 daily backups
- Keep 4 weekly backups
- Keep 6 monthly backups
Use UTC timestamps in filenames.
Timezone changes can otherwise create messy edge cases:
TS=$(date -u +%Y%m%dT%H%M%SZ)
OUT=/var/backups/archives/vps1-$TS.tar.gz.gpg
On the offsite server, you can prune by age.
Example: delete offsite files older than 45 days (adjust to your policy):
find /srv/backups/vps1 -type f -name 'vps1-*.gpg' -mtime +45 -delete
Better than age-only: use a calendar policy (daily/weekly/monthly) that preserves meaningful restore points.
If you don’t want to write retention logic, use a tool that supports it (Borg/restic/rclone with structured rotation).
The tool matters less than having a policy you can describe and test.
Step 9: Automate with a systemd timer (cleaner than cron for logging)
systemd timers are easier to audit than cron.
You get consistent scheduling and logs in journalctl.
Start by writing a script:
sudo tee /usr/local/sbin/backup-run.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
TS=$(date -u +%Y%m%dT%H%M%SZ)
STAGE=/var/backups/staging
ARCH=/var/backups/archives
NAME=vps1-$TS
OFFSITE_HOST=OFFSITE_IP
OFFSITE_USER=backuprecv
OFFSITE_PATH=/vps1
mkdir -p "$STAGE/mysql" "$STAGE/manifests" "$ARCH"
# DB dump (adjust for your stack)
mysqldump --single-transaction --routines --events --all-databases \
| gzip -1 > "$STAGE/mysql/all-databases.sql.gz"
# Small environment manifest
php -v > "$STAGE/manifests/php-version.txt" || true
nginx -v 2> "$STAGE/manifests/nginx-version.txt" || true
apache2ctl -v > "$STAGE/manifests/apache-version.txt" 2>/dev/null || true
# Build archive
TMP="$ARCH/$NAME.tar.gz"
ENC="$ARCH/$NAME.tar.gz.gpg"
tar -czpf "$TMP" --absolute-names \
--exclude-from=/etc/backup-exclude.txt \
-T /etc/backup-include.txt
# Encrypt
gpg --batch --yes --symmetric --cipher-algo AES256 \
--output "$ENC" "$TMP"
# Remove plaintext
shred -u "$TMP"
# Ship offsite
rsync -av --partial --inplace "$ENC" \
"$OFFSITE_USER@$OFFSITE_HOST:$OFFSITE_PATH/"
# Local retention: keep last 14 encrypted archives
ls -1t $ARCH/vps1-*.gpg 2>/dev/null | tail -n +15 | xargs -r rm -f
EOF
sudo chmod 0750 /usr/local/sbin/backup-run.sh
sudo chown root:backupuser /usr/local/sbin/backup-run.sh
Important: For non-interactive GPG symmetric encryption, you’ll need to provide the passphrase via a secure mechanism (GPG agent, protected file readable only by root, or better: switch to public-key encryption).
Don’t hardcode secrets in the script.
Create a systemd service:
sudo tee /etc/systemd/system/backup-run.service >/dev/null <<'EOF'
[Unit]
Description=Nightly backup run (local + offsite)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=backupuser
Group=backupuser
ExecStart=/usr/local/sbin/backup-run.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
EOF
Create a timer (runs daily at 02:15 UTC, with random delay to avoid load spikes):
sudo tee /etc/systemd/system/backup-run.timer >/dev/null <<'EOF'
[Unit]
Description=Schedule nightly backups
[Timer]
OnCalendar=*-*-* 02:15:00 UTC
RandomizedDelaySec=20m
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now backup-run.timer
systemctl list-timers | grep backup-run
Check logs after the first run:
journalctl -u backup-run.service --since "today" -n 200 --no-pager
Step 10: Measure RPO/RTO with a restore rehearsal (don’t skip this)
Don’t wait for an outage to discover your restore is slow or incomplete.
Your first test can be simple and timed.
Decrypt, extract, and verify a few key files. Then do a full staging restore.
Decrypt and list contents (on a staging VPS, not production):
gpg --output /tmp/restore.tar.gz --decrypt vps1-YYYYMMDDTHHMMSSZ.tar.gz.gpg
tar -tzf /tmp/restore.tar.gz | head
Full staging rebuild (recommended): provision a clean VPS, restore files, restore databases, then validate the site behind a temporary hostname or /etc/hosts override.
If you want a disciplined drill format (including DNS cutover and rollback), follow: VPS restore drill tutorial (2026).
Record the numbers you actually achieved:
- RPO actual: time between last successful backup and incident time
- RTO actual: time from “start restore” to “site serving correct content + logins work”
The slow part is rarely file transfer.
It’s the missing glue: DNS, SMTP, cron, TLS, permissions, and small config differences you forgot you ever made.
Step 11: Cover DNS, SSL, and email so restores don’t turn into outages
A file restore is not the same thing as a service restore.
Add these checks to your runbook so recovery doesn’t stall on basics:
- DNS TTL policy: reduce TTL before planned maintenance/migrations
- SSL: restore /etc/letsencrypt (or re-issue certificates if you can’t restore keys safely)
- Email: confirm hostname, PTR/rDNS, SPF/DKIM/DMARC alignment
These two guides tend to pay for themselves during an incident:
- DNS cutover checklist tutorial (2026)
- VPS SSL setup guide tutorial (2026): Let’s Encrypt + safe auto-renew
If your VPS sends mail, include reverse DNS in your recovery notes.
This is the quick path: PTR record setup tutorial (2026).
Step 12: Quick diagnostics when backups “run” but you can’t restore
- Archive is tiny: your include list missed the actual docroot (check vhost configs for real paths).
- GPG decrypt fails: wrong key/passphrase, corrupted upload, or partial file. Validate checksums before upload.
- Restore boots but site 500s: missing PHP modules, wrong ownership, or mismatched PHP-FPM socket path. Use logs to pinpoint quickly.
- Backups consume disk: logs or cache directories slipped into the include list; rotate logs and exclude caches.
When you hit 500 errors during a restore, don’t guess.
Go straight to the logs. This guide stays practical: VPS log analysis tutorial (2026).
Implementation checklist (print this for your runbook)
- Define RPO/RTO targets (per site or per server).
- Document what’s included/excluded and why.
- Encrypt before offsite transfer.
- Store keys/passphrases outside the VPS.
- Offsite account can write new backups but cannot delete history (or deletions require a separate credential).
- Retention policy matches compliance and budget.
- Monthly restore test with measured timings.
- Restore runbook includes DNS/SSL/email steps.
Summary: a backup plan that survives the real failure modes
A workable 3-2-1 setup for a hosting VPS isn’t about chasing a fashionable tool.
It’s about repeatability: stable scope, encrypted offsite copies, retention you can afford, and restore tests you time and record.
If you can restore to a staging server and handle DNS cutover without panic, you’re doing it right.
If you want a stable foundation for automation and offsite workflows, start with HostMyCode VPS for full control, or choose managed VPS hosting when you want the backup and recovery process reviewed by an ops team.
If you’re building a backup plan for client sites or revenue-critical WordPress, run it on infrastructure you can predict. HostMyCode offers VPS hosting for hands-on control and managed VPS hosting when you want help tuning retention, offsite copies, and restore drills.
FAQ: VPS backup strategy tutorial questions (practical)
How often should I back up a WordPress VPS in 2026?
Set frequency based on your RPO.
For busy WooCommerce sites, hourly database dumps plus nightly full archives is common. For low-change sites, nightly backups may be enough.
Should I rely on snapshots only?
No. Snapshots are excellent for quick rollback, but they won’t protect you from full VPS loss or account compromise. Keep an encrypted offsite copy as a separate layer.
Do I need to back up Let’s Encrypt files?
It helps, especially for quick restores.
Back up /etc/letsencrypt and your web server configs, and be prepared to re-issue certificates if key handling is uncertain.
What’s the safest offsite target: another VPS or object storage?
Either can be safe. What matters is access control (prevent mass deletion), encryption, and restore speed. Many teams use object storage for durability and a second VPS for faster retrieval.
How do I prove my backups work without risking production?
Restore onto a fresh staging VPS, verify logins and dynamic pages, then time the process to calculate RTO.
Use a structured drill like the one linked earlier, and update your notes after each run.