VPS Snaps

How to back up a server with rsnapshot

rsnapshot keeps rotating snapshots of your directories: it runs rsync into alpha.0 and turns older copies into hard links, so every snapshot is a complete tree but an unchanged file is stored once. Install it with sudo apt install rsnapshot, list sources in /etc/rsnapshot.conf with tabs between fields, check it with sudo rsnapshot configtest, and run rsnapshot alpha, beta and gamma from cron. To restore, copy files out of a snapshot.

10 min readUpdated Checked against official documentation

How rsnapshot saves space

Each retain line in the config defines a backup level and how many snapshots it keeps. Running the first, most frequent level (say alpha, keeping 6) does this:

  1. Deletes the oldest snapshot, alpha.5.
  2. Renames alpha.4 to alpha.5, and so on down to alpha.1.
  3. Copies alpha.0 to alpha.1 with cp -al: new directories, but every file is a hard link to the same data.
  4. Runs rsync from each source into alpha.0. rsync writes a changed file under a temporary name and renames it into place, which breaks the link, so alpha.1 keeps the old version.

Only the first level reads your files. rsnapshot beta rotates the beta snapshots and moves the oldest alpha, alpha.5, to beta.0; gamma takes the oldest beta.

We replayed these steps with cp -al and rsync on a directory holding a 20 MB video and 50 small pages, changing only index.php between runs. du counts a hard-linked file once when it measures all the directories in one command, as rsnapshot du does:

Terminal
du -csh alpha.0 alpha.1 alpha.2
Output
20M	alpha.0
28K	alpha.1
28K	alpha.2
20M	total

Three complete copies in the space of one. The video is one inode with three names (a link count of 3); index.php is a separate file in each snapshot:

Terminal
stat -c '%i %h %s %n' alpha.*/localhost/var/www/html/uploads/video.mp4 alpha.*/localhost/var/www/html/index.php
Output
2665908 3 20000000 alpha.0/localhost/var/www/html/uploads/video.mp4
2665908 3 20000000 alpha.1/localhost/var/www/html/uploads/video.mp4
2665908 3 20000000 alpha.2/localhost/var/www/html/uploads/video.mp4
2665926 1 17 alpha.0/localhost/var/www/html/index.php
2665915 1 17 alpha.1/localhost/var/www/html/index.php
2665857 1 17 alpha.2/localhost/var/www/html/index.php

Measured one at a time, each snapshot showed the full 20M: du counting the shared file again. Deleting alpha.2 freed only 28K, since two names still pointed at the video.

Install rsnapshot

Terminal
sudo apt install rsnapshot

Ubuntu 24.04 (universe) and Debian 12 ship rsnapshot 1.4.5, Ubuntu 22.04 has 1.4.3 and Debian 13 has 1.5.1. Its /etc/rsnapshot.conf keeps snapshots in /var/cache/rsnapshot/ (mode 700), and /etc/cron.d/rsnapshot has every line commented out, so nothing runs until you schedule it.

Snapshots on the disk you are backing up protect you from deletions and bad edits, not from losing the disk or the server. Put the snapshot root on a separate disk, or run rsnapshot on a backup server that pulls from your servers, as shown below.

Edit /etc/rsnapshot.conf: tabs, not spaces

Fields in rsnapshot.conf must be separated by tabs, and directories need a trailing slash; arguments inside one value, such as the options after ssh_args, take spaces. Editors that turn Tab into spaces cause most rsnapshot errors. cat -A shows tabs as ^I:

Terminal
grep -v '^#' /etc/rsnapshot.conf | cat -A

Output for a file with two mistakes, on the beta line and the last backup line:

Output
config_version^I1.2$
snapshot_root^I/var/cache/rsnapshot/$
retain^Ialpha^I6$
retain  beta    7$
backup^I/etc/^Ilocalhost/$
backup^I/var/www/ localhost/$

Keep the rest of the shipped file and change these lines. Replace its three sample backup lines (/home/, /etc/, /usr/local/) with your own:

/etc/rsnapshot.conf (lines to change)
snapshot_root	/var/cache/rsnapshot/
retain	alpha	6
retain	beta	7
retain	gamma	4
logfile	/var/log/rsnapshot.log
exclude	*.tmp
backup	/etc/	localhost/
backup	/home/	localhost/
backup	/var/www/	localhost/	exclude=cache/
SettingWhat it does
snapshot_rootWhere snapshots live. One filesystem that supports hard links; the 1.5.1 manual says SMB/CIFS shares are not supported.
no_create_rootSet to 1, rsnapshot refuses to run when the snapshot root is missing, so an unmounted backup disk doesn't fill the root disk.
retainA level name and how many snapshots it keeps, most frequent level first. Older configs say interval, a deprecated alias that still works.
logfileWrites a log; Debian's logrotate rule already covers /var/log/rsnapshot.log.
excludePassed to rsync as --exclude, one pattern per line.
backupA source, then a destination relative to the snapshot root. rsync runs with --relative, so /var/www/ lands in alpha.0/localhost/var/www/. A fourth field adds options for that line only, separated by commas.
rsync_long_argsDefault --delete --numeric-ids --relative --delete-excluded, so owners are kept by number.

Pull backups from other servers over SSH

Run rsnapshot on a separate backup server that pulls from the machines it protects, so they hold no key that can reach their backups. It cannot push: the FAQ says a remote snapshot root over SSH is not supported.

On the backup server, uncomment the cmd_ssh line (Debian and Ubuntu ship it commented out) and create a key without a passphrase, since cron can't type one:

Terminal
sudo ssh-keygen -t ed25519 -N '' -f /root/.ssh/rsnapshot_ed25519 -C rsnapshot@backup1

On each server, add the public key to /root/.ssh/authorized_keys, locked to read-only rsync with rrsync, which rsync installs at /usr/bin/rrsync on Debian 12 and Ubuntu 22.04 and 24.04:

/root/.ssh/authorized_keys on web1
command="/usr/bin/rrsync -ro /",restrict ssh-ed25519 AAAAC3Nza... rsnapshot@backup1

-ro / allows reading any path and nothing else; restrict turns off forwarding and terminals. In our test, rrsync accepted rsnapshot's rsync options for a pull and refused a write with sending to read-only server is not allowed. OpenSSH's default PermitRootLogin prohibit-password accepts this key.

Connect once as root to accept the host key after checking its fingerprint. rrsync then refuses the command with SSH_ORIGINAL_COMMAND does not run rsync, proving the restriction works:

Terminal
sudo ssh -i /root/.ssh/rsnapshot_ed25519 [email protected] true

Then add the key and the remote sources, one destination directory per server:

/etc/rsnapshot.conf
ssh_args	-i /root/.ssh/rsnapshot_ed25519
backup	[email protected]:/etc/	web1/
backup	[email protected]:/var/www/	web1/
backup	[email protected]:/var/backups/db/	web1/

For another SSH port, add -p 2222 to ssh_args. A key locked to rrsync can't run commands, so dump that server's databases from cron on the server itself, shortly before the pull, into the directory you back up. Key basics: SSH key authentication.

Include database dumps

Copying a running database's files can catch them mid-write. Back up a dump instead. backup_script runs a script in an empty temporary directory and stores whatever it writes there under its destination:

/usr/local/bin/rsnapshot-pgdump.sh
#!/bin/sh
# rsnapshot runs this in an empty temporary directory and keeps what it writes here.
set -eu
umask 077
runuser -u postgres -- pg_dump myapp > myapp.sql
Terminal
sudo chmod 700 /usr/local/bin/rsnapshot-pgdump.sh
/etc/rsnapshot.conf
backup_script	/usr/local/bin/rsnapshot-pgdump.sh	localhost/postgres/

If the script fails, rsnapshot logs an error and keeps that directory from the previous snapshot; an unchanged dump is hard-linked like any file. The script runs as root, so only root may write to it.

backup_exec runs any command, in config-file order with the backup lines. With required, a failure stops the run; the default, optional, only warns:

/etc/rsnapshot.conf
backup_exec	/usr/local/bin/dump-databases.sh	required
backup	/var/backups/db/	localhost/

cmd_preexec and cmd_postexec run before and after the first level's backup only, never on rotations, and stop rsnapshot if they fail. Dump details: pg_dump, mysqldump.

Test the configuration

Terminal
sudo rsnapshot configtest

It prints Syntax OK or each problem with its line number. A test run prints the mv, cp -al and rsync commands without running them:

Terminal
sudo rsnapshot -t alpha

Take the first real snapshot and look inside:

Terminal
sudo rsnapshot alpha
Terminal
sudo ls /var/cache/rsnapshot/alpha.0/localhost/

rsnapshot exits 0 on success, 1 on a fatal error and 2 when the backup finished with warnings, such as an rsync partial transfer (code 23) or files that vanished during the copy (code 24). sudo rsnapshot du reports the space all snapshots use, counted correctly.

Schedule the levels

Uncomment the lines in /etc/cron.d/rsnapshot that match your retain names:

/etc/cron.d/rsnapshot
0 */4 * * *   root  /usr/bin/rsnapshot alpha
30 3  * * *   root  /usr/bin/rsnapshot beta
0 3   * * 1   root  /usr/bin/rsnapshot gamma

With retain 6, 7 and 4, that keeps a snapshot every 4 hours for a day, one a day for a week and one a week for four weeks: 17 snapshots reaching back about a month.

  • Each level's count times its interval should cover the next level's interval: 6 alphas 4 hours apart make a day, 7 betas a week.
  • Run larger levels a little before smaller ones, as the manual advises: beta then takes the oldest alpha before alpha would delete it. Leave the rotation time to finish, or alpha finds the lock taken.
  • A higher level has nothing to take until the one below is full, so expect messages in the first day (beta) and week (gamma).

cron mails any output to root if the server can send mail. For systemd timers, give each level its own service and timer (the timers guide) with the same staggered times.

Restore files

Snapshots are ordinary directories, so a restore is a copy. To find what changed between two snapshots, run diff as root from /var/cache/rsnapshot:

Terminal
diff -rq alpha.2/localhost/var/www alpha.0/localhost/var/www
Output
Files alpha.2/localhost/var/www/html/index.php and alpha.0/localhost/var/www/html/index.php differ

Copy one file back with cp -a, which keeps its owner, mode and modification time:

Terminal
sudo cp -a /var/cache/rsnapshot/alpha.2/localhost/var/www/html/index.php /var/www/html/index.php

Restore a directory into a new path first, then compare it with the live one:

Terminal
sudo rsync -a /var/cache/rsnapshot/beta.0/localhost/var/www/ /root/restore/www/
Terminal
sudo diff -rq /root/restore/www /var/www

For a remote server, push back with an admin key (the rrsync key is read-only), keeping owners by number. Preview with -ani first, as in the rsync guide:

Terminal
sudo rsync -a --numeric-ids /var/cache/rsnapshot/alpha.0/web1/var/www/ [email protected]:/var/www/

What rsnapshot does not do

  • No encryption or compression. Snapshots are plain files, readable by root on the backup server.
  • No off-site copy. Snapshots share one copy of each unchanged file on one disk, so a damaged block hits every snapshot linking to it. Add an encrypted copy elsewhere, for example with restic, to meet the 3-2-1 rule.
  • One filesystem. Hard links can't cross filesystems, so all levels share one local or NFS snapshot root. No S3 or SMB.
  • No point-in-time copy. A file being written can be caught mid-write. Dump databases; for a frozen view of a volume, see LVM snapshots.
  • Slow rotations with millions of files. Each cp -al and each deletion touches every file name; use_lazy_deletes set to 1 deletes after the lock is released.

Common errors

Each line starts with ERROR: and long ones break with a backslash. Config errors begin ERROR: /etc/rsnapshot.conf on line 12: and are followed by Errors were found in /etc/rsnapshot.conf,.

MessageCauseFix
missing tabs to separate words - change spaces to tabs.Spaces between the first two fieldsUse tabs; check with cat -A.
no destination path specified for backup pointSpaces between source and destinationA tab between them.
Cannot handle [email protected]:/etc/, cmd_ssh not defined in /etc/rsnapshot.confRemote source with cmd_ssh commented out, as shippedUncomment the cmd_ssh line.
Backup destination /backup/web1/ must be a local, relative pathAbsolute destinationUse web1/; it is relative to the snapshot root.
Interval "daily" unknown, check /etc/rsnapshot.confcron runs a level with no retain line, often the old hourly/daily namesMake cron and retain names match.
Lockfile /var/run/rsnapshot.pid exists and so does its process, can not continueThe previous run is still goingSpace out the schedule; look for a hung SSH session.
rsnapshot refuses to create snapshot_root when no_create_root is enabledThe backup disk isn't mountedMount it.
/usr/bin/rsync returned 255 while processing [email protected]:/etc/SSH failed, often Host key verification failed. under cron, or a refused keyConnect once by hand as root; check the key path in ssh_args.
/var/cache/rsnapshot/alpha.5 not present (yet), nothing to copy (1.4) or Did not find previous interval max (/var/cache/rsnapshot/alpha.5), refusing to rotate this level (beta) (1.5)beta ran before alpha had 6 snapshotsNormal at first; it stops once alpha is full.

Frequently asked questions

Where did rsnapshot's hourly and daily levels go?
Since rsnapshot 1.4 the sample config names its levels alpha, beta, gamma and delta instead of hourly, daily, weekly and monthly. Names are yours to choose: hourly and daily still work if cron uses the same names.
Does rsnapshot encrypt or compress backups?
No. Snapshots are plain files, which is what makes restores a simple copy. Protect the backup disk, and use restic or BorgBackup for encrypted copies.
Can rsnapshot back up to S3 or a remote server?
Not directly: the snapshot root must be local or NFS-mounted. Run rsnapshot on the backup server and pull, and send an encrypted copy to object storage with another tool.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: