
A backup you’ve never restored is just a theory. This VPS backup restore tutorial walks you through a realistic, end-to-end restore drill on Ubuntu.
You’ll restore files, databases, cron jobs, and web server config. Then you’ll verify DNS and TLS behavior without touching your live site.
By the end, you’ll have a tight runbook for real incidents. That includes bad deploys, compromised hosts, and disk failures. You’ll also have a repeatable way to prove your restores actually work.
What you’ll build (and what you won’t)
- Build: a restore-first runbook, a staging restore host, and a verification checklist.
- Restore: web root, database, systemd services, cron jobs, and Nginx/Apache vhosts.
- Validate: HTTP response, log health, database connectivity, TLS correctness, and DNS cutover readiness.
- Not covered: control-panel specific restores (WHM/cPanel full account restore). If you run WHM, use the dedicated guide below.
If you manage your own VPS or dedicated server, this workflow maps cleanly.
If you want the same process with less on-call stress, consider managed VPS hosting from HostMyCode. Your restore plan still matters, but you’re not doing it alone when time is tight.
Prerequisites and lab layout
This tutorial assumes:
- Ubuntu Server 24.04 LTS or 26.04 LTS on production (steps are the same).
- Nginx or Apache, plus PHP-FPM if you run PHP apps (WordPress, Laravel, etc.).
- Backups stored offsite (object storage or another VPS).
- A domain you control (to test DNS and TLS safely).
Recommended lab layout:
- Prod VPS: the server you’re protecting.
- Restore VPS: a temporary Ubuntu VPS where you rehearse restores.
For the restore host, choose a small instance that matches your stack. A basic HostMyCode VPS works for most rehearsal restores.
If you’re restoring a large WooCommerce site, match RAM and disk more closely. Otherwise, imports can crawl or time out.
Safety rule: don’t run a drill on production. Restore onto a separate host, validate, then decide whether a cutover makes sense.
Step 1 — Inventory what “a full restore” means for your server
Many teams restore only /var/www and a database dump. Then they spend hours chasing missing jobs, broken mail, or weird permissions.
Start by listing every moving piece you expect to survive a rebuild.
Quick inventory checklist
- Web files:
/var/wwwor your app path. - Database: MySQL/MariaDB dump or physical backup (plus users/privileges).
- Web server config: Nginx
/etc/nginxand/or Apache/etc/apache2. - PHP config:
/etc/php/*/fpmand pool files. - TLS material: Let’s Encrypt
/etc/letsencrypt(optional—often better to re-issue on cutover). - App secrets: environment files, API keys,
wp-config.php. - Cron jobs:
/etc/cron*, per-user crontabs. - Systemd units:
/etc/systemd/systemfor custom services. - Uploads: user-generated content (often the largest, easiest to miss).
If your restore plan includes DNS changes, treat DNS as its own step. Give it dedicated checks and clear rollback notes.
HostMyCode’s migration checklist style is helpful here. See this staged cutover migration tutorial for the same “rehearse first” approach.
Step 2 — Prepare a clean restore VPS (same OS family, minimal extras)
Start with a fresh Ubuntu host. Set up access the same way you run production.
Use a non-root admin user and SSH keys. This keeps the drill close to real operations.
If you need a refresher, use this sudo setup guide.
Baseline packages
sudo apt update
sudo apt -y install rsync curl unzip jq
Install the same stack you run in production. Example for Nginx + PHP-FPM + MariaDB:
sudo apt -y install nginx mariadb-server php-fpm php-mysql
sudo systemctl enable --now nginx mariadb php8.3-fpm
Use the PHP version your app expects.
In 2026, PHP 8.3 is still common on Ubuntu hosting stacks, and some sites have moved to PHP 8.4.
Don’t “upgrade during the restore” unless that’s part of the plan.
Step 3 — Pull the backup artifacts and verify integrity before restoring
Don’t overwrite anything until you’ve confirmed the backup is complete and readable.
A restore drill is not the time to discover a corrupt archive.
Example backup bundle
site-files.tar.zstdb.sql.zst(ordb.sql.gz)etc-nginx.tar.zstoretc-apache2.tar.zstcron.tar.zst
Install Zstandard if you use it (recommended for speed):
sudo apt -y install zstd
Integrity checks (strongly recommended)
If you publish checksums alongside backups:
sha256sum -c SHA256SUMS.txt
If you don’t, add checksums to your backup job on the next cycle. It’s one of the cheapest reliability upgrades you can make.
For retention, encryption, and automated restore tests, HostMyCode has a dedicated guide: VPS backup verification tutorial.
This post stays focused on the hands-on, full restore drill.
Step 4 — Restore web files with correct ownership and permissions
Restore into the path your vhost already expects. This keeps recovery simple.
Avoid “quick” config edits that turn into permanent surprises.
Restore files
sudo mkdir -p /var/www/example.com
sudo tar --use-compress-program=zstd -xf site-files.tar.zst -C /var/www/example.com
Fix ownership (common pitfall)
If you run PHP-FPM pools per site user, your owner might be example. If you run a shared pool, it’s usually www-data.
# Example: typical Nginx/Apache user
sudo chown -R www-data:www-data /var/www/example.com
Quick diagnostic: if you hit 403 errors right after restore, check directory execute bits:
namei -l /var/www/example.com
Step 5 — Restore the database (and avoid charset surprises)
For MariaDB/MySQL logical dumps, create an empty database, create the user, then import.
Keep credentials aligned with your app config. Otherwise, you’ll waste time debugging the wrong problem.
Create database and user
sudo mariadb
CREATE DATABASE exampledb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'exampleuser'@'localhost' IDENTIFIED BY 'REPLACE_WITH_STRONG_PASSWORD';
GRANT ALL PRIVILEGES ON exampledb.* TO 'exampleuser'@'localhost';
FLUSH PRIVILEGES;
Import dump
zstd -dc db.sql.zst | mariadb -u exampleuser -p exampledb
Common restore failure: “Unknown collation” or “Incorrect string value”. This usually means the dump came from a newer DB version than your restore host.
The clean fix is to match DB major versions between prod and restore, or rebuild the dump with compatible settings.
Step 6 — Restore Nginx/Apache vhost config and enable the site
This is where many “restores” fall apart. If your web server config wasn’t backed up, the site won’t come up cleanly.
If the site isn’t enabled on the new host, you’ll get a default page and a false sense of progress.
Nginx example restore
sudo tar --use-compress-program=zstd -xf etc-nginx.tar.zst -C /
Validate config and reload:
sudo nginx -t
sudo systemctl reload nginx
Apache example restore
sudo tar --use-compress-program=zstd -xf etc-apache2.tar.zst -C /
sudo apachectl -t
sudo systemctl reload apache2
Pitfall: if your vhost references a specific PHP-FPM socket path, confirm it exists.
On Ubuntu it commonly looks like:
/run/php/php8.3-fpm.sock- or a pool socket like
/run/php/php8.3-fpm-example.sock
Step 7 — Restore cron jobs and background workers
A site can look fine in a browser while cron is completely dead. Then orders don’t sync, queues back up, and scheduled jobs never fire.
Restore cron like you mean it.
Restore system crons
sudo tar --use-compress-program=zstd -xf cron.tar.zst -C /
Restore per-user crontab (example)
If you backed up crontabs using crontab -l into files:
sudo crontab -u www-data /root/backups/www-data.cron
Quick cron validation
systemctl status cron --no-pager
grep CRON /var/log/syslog | tail -n 30
Step 8 — Bring the restored site up safely (hosts-file test first)
Before you change public DNS, test from your workstation with a hosts-file override.
This keeps the drill invisible to real users. It also prevents partial cutovers while you’re still fixing issues.
Get restore VPS IP
curl -4 ifconfig.me
Temporary hosts entry (your laptop)
# /etc/hosts (macOS/Linux) or C:\Windows\System32\drivers\etc\hosts (Windows)
203.0.113.50 example.com www.example.com
Now load https://example.com in your browser. If TLS isn’t ready, test over HTTP first.
You can also use a temporary self-signed cert to confirm routing and app behavior.
Step 9 — Validate logs, health, and content correctness
“Homepage loads” isn’t a restore test. You’re proving the site can operate normally.
Minimum validation checklist
- HTTP status: key pages return 200/301, not 500.
- PHP-FPM: no “Primary script unknown” errors.
- Database connectivity: login/admin works.
- Uploads: images and media render.
- Write paths: cache/session/upload directories writable.
- Background tasks: cron/queue actually runs.
Commands you’ll actually use
# Nginx access/error logs (paths may vary by site)
sudo tail -n 80 /var/log/nginx/error.log
sudo tail -n 80 /var/log/nginx/access.log
# PHP-FPM logs (version-specific)
sudo journalctl -u php8.3-fpm --no-pager -n 120
# Disk and inode sanity
df -h
df -i
If you want a simple monitoring baseline for restore hosts and production alike, use HostMyCode’s server monitoring tutorial.
Add a “restore drill” tag/checklist to your alerts so the same checks fire every time.
Step 10 — Handle SSL/TLS correctly during cutover (don’t copy mistakes)
Copying /etc/letsencrypt from production can work. But it can also create easy-to-miss problems.
Common issues include the wrong chain, missing renewal hooks, or certs tied to old validation methods. In most cases, re-issue after DNS cutover or use DNS validation.
Option A: Re-issue Let’s Encrypt after cutover (cleanest)
Once DNS points to the restore host, run Certbot (or your preferred ACME client) and issue fresh certs.
If you need a step-by-step, follow: SSL setup guide for Nginx.
Option B: Keep TLS stable during a fast rollback
If speed matters (and you control the full chain), restoring existing certs may be acceptable.
Verify the served cert and chain before you call the restore “done”:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject -dates
If you hit handshake or chain issues during the drill, use this TLS handshake troubleshooting tutorial.
Step 11 — Practice the DNS cutover and rollback plan (without guessing)
Incidents go sideways when DNS steps live in someone’s head.
Missing AAAA records, stale TTLs, and undocumented TXT records are common failure points.
Write the process down. Then practice it.
Cutover prep
- Lower TTL on A/AAAA records (typical: 300 seconds) at least a few hours before a planned drill.
- Export the current DNS zone as a rollback snapshot.
- List every record you must keep: A/AAAA, CNAME, MX, TXT (SPF/DKIM/DMARC), SRV if used.
If you want the exact procedure for low-downtime record changes and rollback, follow HostMyCode’s DNS cutover tutorial. It pairs well with this restore drill.
DNS verification commands
# Check current A/AAAA results
dig +short A example.com
dig +short AAAA example.com
# Check authoritative nameservers
dig NS example.com +short
# Confirm what a resolver sees (use a known public resolver)
dig @1.1.1.1 example.com +short
dig @8.8.8.8 example.com +short
Step 12 — Document your restore runbook (keep it short and executable)
A runbook isn’t “nice to have” documentation. It’s the checklist you follow when you’re tired, stressed, and working from a terminal at 2 a.m.
Keep it short enough that you’ll actually use it.
Runbook template (copy/paste)
- Decision: restore to new host or repair in place?
- Access: who has SSH keys, where are credentials stored?
- Restore order: OS baseline → web files → database → config → cron → TLS.
- Validation: URLs to test, admin login, background jobs, payment/email checks.
- Cutover: DNS changes, TTL targets, expected propagation time, rollback record set.
- Post-restore: rotate secrets, re-enable backups, confirm monitoring and alert routes.
Practical troubleshooting during restore (fast fixes)
These are the problems that show up again and again during restore drills.
500 errors right after restore
- Check Nginx/Apache syntax:
nginx -torapachectl -t - Check PHP-FPM status:
systemctl status php8.3-fpm - Tail logs:
/var/log/nginx/error.logor Apache error log
WordPress shows the old URL or mixed content
- Verify
siteurlandhomesettings in the database. - If using a hosts-file test, some plugins still hardcode canonical URLs. Test using curl with Host header if needed.
Permissions issues on uploads/cache
- Confirm correct owner/group for the runtime user.
- Ensure directories are 755 and files 644 unless your app requires stricter settings.
SSH locked out on restore host after firewall changes
In a live incident, this is how you lose time fast.
Keep HostMyCode’s guide handy: firewall troubleshooting tutorial.
Operational extras that make restores faster next time
- Standardize paths: the same vhost layout across servers reduces restore edits.
- Store config in backups: at least
/etc/nginxor/etc/apache2plus PHP-FPM pool files. - Version pinning: record your key versions (OS, PHP, DB, Nginx/Apache) in the runbook.
- Keep DNS tidy: document every record that must survive a cutover.
If you run client sites or revenue-critical apps, rehearse restores on a second VPS the same way you’d respond to a real outage. HostMyCode keeps it simple: spin up a small HostMyCode VPS as a restore sandbox, or use managed VPS hosting when you want experienced hands available during recovery windows.
FAQ
How often should I run a restore drill?
Quarterly is a solid baseline for most hosting workloads. Run it monthly if you deploy often, or if your site changes daily (ecommerce, membership, high-content publishers).
Should I restore onto the same VPS or a new one?
For drills, use a new restore VPS so you don’t risk production. During real incidents, a new host is often safer when compromise is suspected.
Do I need to back up /etc/letsencrypt?
Usually no. Re-issuing certificates after cutover is cleaner. Back it up only if you’ve validated your renewal flow and you need very fast rollbacks.
What’s the fastest way to verify the restore is “good”?
Test three things: a dynamic page (hits PHP), a media file (uploads), and a logged-in workflow (admin or checkout). Then check error logs for 5 minutes under light load.
I use WHM/cPanel. Is this still relevant?
The mindset is the same, but the mechanics differ. Use HostMyCode’s cPanel-specific restore guide for account-level restores: cPanel account restore tutorial.
Summary: your restore plan is a product, not a checkbox
This restore drill turns backups into something you can rely on. You get a documented sequence, a safe place to test, and validation steps that catch the usual failures.
Run it, time it, and tighten the parts that slow you down.
When you need a clean sandbox for rehearsals—or support executing restores under pressure—use a HostMyCode VPS for your restore environment or choose managed VPS hosting to reduce operational risk while keeping full control of your stack.