A backup strategy for your VPS
Updated
A good VPS backup strategy has three layers: automatic server backups for quick full restores, application-level dumps for databases, and an encrypted offsite copy at a second location. Add a snapshot before every risky change, and test a restore regularly, because an untested backup is only a hope.
This guide explains each layer, with commands for Ubuntu 24.04 and Debian 12.
Start with the 3-2-1 rule
The classic rule still holds:
- 3 copies of your data (the live one plus two backups),
- on 2 different kinds of storage or systems,
- 1 of them offsite, at another location or provider.
On a VPS that usually means: the server itself, the provider's backups, and an encrypted copy somewhere else.
Know what you are protecting against
| Risk | What helps |
|---|---|
| A bad update or config change | Snapshot before the change |
| Deleted files, broken app | Daily backups with several restore points |
| Database corruption | Logical dumps (pg_dump, mariadb-dump) |
| Compromised server or ransomware | Offsite copies the server cannot delete |
| Lost account access | Copies held outside the account |
Different risks need different layers. That is why one method is never enough for important data.
Layer 1: provider backups and snapshots
Automatic backups
On Wolkra, you can turn on daily automatic backups per server. They cost 20% of the server price and keep 7 restore points. A backup captures the whole server, so a restore brings back the OS, configuration and data together. That makes it the fastest way back after a serious mistake.
Snapshots
Snapshots are taken on demand and billed per GB. Use them:
- before upgrading the OS, a database or a major app version;
- before experimenting with firewall or network settings;
- as a template: you can create new servers from a snapshot.
See backups and snapshots and the docs for the steps.
Make disk-level copies consistent
A backup of a running server is like pulling the power plug: files are consistent at the file system level, but a database in the middle of a write may need recovery. Databases are built to handle that, but a clean dump (layer 2) removes the doubt.
Layer 2: application-level dumps
PostgreSQL
sudo mkdir -p /var/backups/db && sudo chown postgres: /var/backups/db
sudo -u postgres sh -c 'pg_dump -Fc mydb > /var/backups/db/mydb-$(date +%F-%H%M).dump'
-Fc is the compressed custom format; restore it with pg_restore.
MariaDB or MySQL
sudo sh -c 'mariadb-dump --single-transaction --routines --all-databases | gzip > /var/backups/db/all-$(date +%F-%H%M).sql.gz'
--single-transaction gives a consistent dump of InnoDB tables without locking them.
In Docker
docker compose exec -T db pg_dump -U app app | gzip > ~/backups/app-$(date +%F).sql.gz
Schedule and prune
Put the dump in a script and run it from a systemd timer or cron, then remove old files:
find /var/backups/db -type f -mtime +14 -delete
Layer 3: encrypted offsite copies with restic
restic encrypts, deduplicates and sends backups to many targets: another server over SFTP, or any S3-compatible object storage.
Install
sudo apt install -y restic
Choose a repository
The examples use SFTP to a backup server you control. An S3-compatible bucket works the same way with s3: repository URLs and credentials in environment variables.
sudo mkdir -p /etc/restic && sudo chmod 700 /etc/restic
openssl rand -base64 32 | sudo tee /etc/restic/password >/dev/null
sudo chmod 600 /etc/restic/password
sudo tee /etc/restic/env >/dev/null <<'EOF'
RESTIC_REPOSITORY=sftp:[email protected]:/srv/restic/web-1
RESTIC_PASSWORD_FILE=/etc/restic/password
EOF
sudo chmod 600 /etc/restic/env
Root needs an SSH key that can log in to backup.example.com (see SSH keys). Store the restic password somewhere outside the server too. Without it the backups cannot be read by anyone, including you.
Initialise and run
sudo bash -c 'set -a; . /etc/restic/env; restic init'
sudo bash -c 'set -a; . /etc/restic/env; restic backup /etc /var/www /var/backups/db /home --exclude-caches'
sudo bash -c 'set -a; . /etc/restic/env; restic snapshots'
Retention
sudo bash -c 'set -a; . /etc/restic/env; restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune'
That keeps a week of dailies, a month of weeklies and half a year of monthlies.
Automate with a systemd timer
/etc/systemd/system/restic-backup.service:
[Unit]
Description=restic backup
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
EnvironmentFile=/etc/restic/env
ExecStart=/usr/bin/restic backup /etc /var/www /var/backups/db /home --exclude-caches
ExecStartPost=/usr/bin/restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
/etc/systemd/system/restic-backup.timer:
[Unit]
Description=Nightly restic backup
[Timer]
OnCalendar=*-*-* 03:30
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer
Run the database dump before restic, for example with an ExecStartPre= line that calls your dump script.
Protect against a compromised server
If an attacker gets root, they can delete backups the server can reach. Reduce that risk:
- give the backup user on the target append-only access where the tool supports it (restic's REST server has an
--append-onlymode); - run pruning from a different machine;
- keep at least one copy in a place the server has no credentials for.
What to back up
/etcfor configuration.- Application code and uploads:
/var/www,/srv,/optor your app directory. - Database dumps, not raw database files.
- Docker volumes for stateful containers, or their dumps.
- TLS and app secrets, encrypted.
- A short
READMEthat says how to rebuild the server.
You usually do not need /var/cache, /tmp, package caches or Docker image layers that can be pulled again.
Block storage volumes
If your data lives on a block storage volume, the volume can be detached and attached to a new server, which helps with server failures. It is still one copy, so include it in restic or dumps like any other data.
Test the restore
Pick a schedule, for example monthly, and do one of these:
- Full server: create a new server from a backup or snapshot, start it on a test name and check that the app works.
- Files: restore to a temporary directory and compare.
sudo bash -c 'set -a; . /etc/restic/env; restic restore latest --target /tmp/restore-test --include /etc/caddy'
sudo bash -c 'set -a; . /etc/restic/env; restic check'
- Database: load a dump into a fresh database and run a few queries.
With hourly billing, a restore test server for an hour costs very little. Write down how long the restore took; that is your real recovery time.
A simple plan to copy
| What | How | Retention |
|---|---|---|
| Whole server | Provider daily backups | 7 restore points |
| Before changes | Snapshot | Delete after a week |
| Databases | Dump every 6 hours | 14 days local |
| Files and dumps | restic, nightly, offsite | 7 daily, 4 weekly, 6 monthly |
| Restore test | New server from backup | Monthly |
Adjust the numbers to how much data you can afford to lose and how fast you need to be back. For the rest of the server setup see first steps on a new VPS and how to secure an Ubuntu server.
Frequently asked questions
What is the difference between a snapshot and a backup?
A snapshot is a point-in-time copy you take by hand, often before a risky change. Backups run automatically on a schedule and keep several restore points. Both protect against mistakes; neither replaces an offsite copy.
How often should I back up a VPS?
At least daily for anything that changes. Databases with important data often need more frequent dumps, sometimes hourly.
Are provider backups enough?
They are a strong first layer and the fastest way to restore a whole server. For important data, add a copy at another provider or location so one account or one site is not a single point of failure.
Should backups be encrypted?
Yes, especially copies stored outside your own servers. Tools like restic encrypt everything before it leaves the server.
How do I know my backups work?
Restore them. Create a new server from a backup or restore files to a test directory on a schedule, and check that the app actually starts.