Skip to content

The publication for web craftspeople Tuesday, 15 September 2026

DevOps & servers

Automated backups for a self-hosted web server: a method that holds

A self-hosted server without a tested backup is an outage waiting to happen. This method covers the database, files, encryption, rotation and verified restore of a WordPress or PHP VPS.

Self-hosting a site on a VPS costs a few euros a month and hands back control of the whole stack. In exchange, backups become a full responsibility: no shared host watches in the background. A server without a tested backup is not merely at risk, it is a deferred outage. This guide lays out a complete, repeatable method for a WordPress or PHP site: database, files, encrypted off-site copy, rotation and, above all, a verified restore.

Why the “when I remember” backup always fails

A manual backup rests on a repeated human decision, exactly the kind of task a busy schedule drops first. It fails in three predictable ways: it is not run often enough, it lives on the same disk as the site — so it vanishes with it — and it is never restored to check that it works. A backup that is never tested is only a hypothesis. A good method takes the human out of the daily loop and calls them back only for a monthly restore test.

The 3-2-1 rule, small-server edition

The 3-2-1 rule remains the sturdiest reference: three copies of the data, on two different media, one of them off-site. On a modest VPS it works without expensive hardware.

CopyLocationRole
Production dataVPS diskThe live source
Local backupSecond volume or dedicated folderFast restore
Off-site backupEncrypted object storage (S3, B2)Survives losing the VPS

Two points matter more than the rest: the off-site copy must be encrypted before it leaves the server, and the local copy never replaces the off-site one. An incident that destroys the VPS takes both volumes if they sit on the same machine.

Backing up the database cleanly

For a dynamic site the database holds the essentials: content, accounts, orders. A consistent dump runs without locking tables, using a single transaction on a transactional engine such as InnoDB.

#!/usr/bin/env bash
# Sauvegarde de la base : dump compresse, date dans le nom
set -euo pipefail
STAMP=$(date +%F_%H%M)
DEST=/var/backups/db
mkdir -p "$DEST"
mariadb-dump --single-transaction --quick --lock-tables=false \
  --defaults-extra-file=/root/.my.cnf ma_base \
  | gzip -9 > "$DEST/ma_base_$STAMP.sql.gz"

The ~/.my.cnf credentials file keeps the password out of the command, and therefore out of shell history and the process table. It should be readable only by the account that backs up, with 600 permissions.

Backing up the site files

Not every file is equal: code and uploaded media must be saved, while caches and logs are worthless and needlessly bloat the archive. An incremental rsync to a local volume is fast and readable.

# Fichiers du site : incrementiel via rsync vers un disque local
rsync -aH --delete \
  --exclude 'wp-content/cache' \
  --exclude '*.log' \
  /var/www/monsite/ /var/backups/files/monsite/

The --delete flag keeps the local mirror strictly aligned with the source; use it knowingly, since a deletion on the site propagates to the local copy. That is exactly why the local copy is not enough: retention lives at the off-site level.

Off-site and encrypted with restic

The off-site copy is the one that saves you from a data-centre fire or a compromised account. restic encrypts client-side, deduplicates and speaks object storage natively — the archive leaves unreadable to the repository host.

# Externaliser + chiffrer avec restic (depot sur stockage objet S3)
export RESTIC_REPOSITORY="s3:https://s3.example.com/backups-monsite"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID=...  AWS_SECRET_ACCESS_KEY=...

restic backup /var/backups/db /var/backups/files --tag nightly
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

The forget --prune command applies the retention policy and actually frees the space of discarded snapshots. The repository password lives in a file outside the repository: lose it and the backups become permanently unreadable.

Where to store the off-site copy

The choice of off-site repository weighs on both cost and restore speed. Three families stand out for a self-hosted site. S3-compatible object storage — Backblaze B2, Scaleway, OVH, Wasabi — bills per gigabyte stored and stays the most flexible; egress pricing varies sharply between providers and is worth reading before committing. A backup-focused service such as rsync.net exposes SSH access and pairs well with restic or a plain rsync. Finally, a second server you already control can act as the repository, provided it is physically separate from the first. The deciding rule stays the same: the provider must never be able to read the content, which restic’s client-side encryption guarantees whatever the repository.

Automating with cron and setting retention

Gathered into a single script, these steps schedule in one line. A quiet night limits the dump’s impact on the production database.

# /etc/cron.d/backup-monsite  -> 3h15 chaque nuit, journalise la sortie
15 3 * * * root /usr/local/sbin/backup-monsite.sh >> /var/log/backup.log 2>&1

Retention balances storage cost against history depth. The table below suits a typical content site.

Kept frequencyDuration
Daily7 days
Weekly4 weeks
Monthly6 months

This policy keeps about thirty snapshots, enough to recover from a late-discovered corruption without blowing up the object-storage bill.

Testing the restore: the step everyone skips

It is the only check that proves the chain works. Once a month, a restore into a throwaway folder is enough to validate archive integrity and keep the muscle memory alive, so it is not discovered on a real outage day.

# Tester la restauration dans un dossier jetable, une fois par mois
restic snapshots
restic restore latest --target /tmp/restore-test
gunzip 

A backup that is never restored is not a backup, it is a hypothesis.

Marker. Two figures frame any recovery plan: the RPO, how much data you accept losing (here, up to 24 h with a daily rhythm), and the RTO, the target time back in service. Halving them costs more — more frequent backups, repeated restores — and that trade-off is decided before the incident, not during it.

The takeaway

A reliable backup comes down to four requirements: automated so it depends on no one, separated from the server so it survives its loss, encrypted before it leaves, and restored regularly to prove it is worth something. The 3-2-1 rule, a cron script and a monthly test cover most cases of a self-hosted site.

The only real backup scare I have lived through came not from a dead disk but from a corrupt dump nobody had ever reopened: the file existed, it was empty. Since then I treat an untested backup as non-existent, and in client setups I put the monthly restore reminder in place before the automation itself. For a content site, restic to object storage at a few euros a month is the best peace-of-mind-per-euro I know. — Simon Janvier

Further reading

Official restic documentation, repositories and retention policies: restic.readthedocs.io.

Read next