rsnapshot is a backup tool built on top of rsync. Each run produces a directory that looks like a full copy of your data, but files that did not change since the previous snapshot are stored as hard links, so they take no extra space. You get a browsable history of daily, weekly and monthly backups for little more than the size of one copy plus the changes.

In this tutorial you will install rsnapshot on an Ubuntu 24.04 backup server, keep snapshots of its own configuration and data, pull backups from a second server over SSH with a read-only key, include a consistent database dump, schedule everything with cron, and restore files from a snapshot.

Prerequisites

To follow this tutorial, you will need:

  • A server running Ubuntu 24.04 LTS that will store the backups, with a non-root user with sudo privileges. A CubePath VPS with an additional block storage volume works well for this.
  • A dedicated disk or volume for the backups, formatted with a Linux filesystem that supports hard links (ext4 or XFS) and mounted at /mnt/backup. Keeping backups on the same disk as the data does not protect you against disk failure.
  • Optionally, a second Ubuntu server to back up remotely. This tutorial calls it web1 with the address web1_ip.

All rsnapshot commands run as root, because rsnapshot needs to read every file and preserve ownership.

How rsnapshot stores snapshots

rsnapshot keeps a numbered directory per retention level inside a snapshot_root:

DirectoryContents
daily.0The most recent daily snapshot
daily.1 to daily.6The six previous days
weekly.0 to weekly.3The last four weeks
monthly.0 to monthly.5The last six months

Only the lowest level (here daily) actually copies data with rsync. When rsnapshot weekly runs, it does not contact any server: it moves the oldest daily snapshot (daily.6) to weekly.0. The same happens between weekly and monthly. This is why the lower level must fill up before the higher levels appear.

Step 1 - Installing rsnapshot

rsnapshot is in the Ubuntu repositories and pulls in rsync as a dependency:

sudo apt update
sudo apt install -y rsnapshot

Check the installed version:

rsnapshot version
rsnapshot 1.4.5

Confirm that the backup volume is mounted where you expect it:

findmnt /mnt/backup
TARGET      SOURCE    FSTYPE OPTIONS
/mnt/backup /dev/vdb1 ext4   rw,relatime

Create the snapshot root on that volume and restrict it to root, since snapshots contain copies of sensitive files like /etc/shadow:

sudo mkdir /mnt/backup/snapshots
sudo chmod 700 /mnt/backup/snapshots

Step 2 - Configuring rsnapshot

rsnapshot reads /etc/rsnapshot.conf. Keep a copy of the original before editing it:

sudo cp /etc/rsnapshot.conf /etc/rsnapshot.conf.orig
sudo nano /etc/rsnapshot.conf

Find and change the following directives. Most already exist in the file, some commented out; leave the rest of the defaults as they are.

Set the snapshot root and stop rsnapshot from creating it if it is missing. With no_create_root, a backup volume that failed to mount makes rsnapshot abort instead of silently filling your root filesystem:

snapshot_root	/mnt/backup/snapshots/
no_create_root	1

Define the retention levels, from the most to the least frequent. The Ubuntu default file uses the names alpha, beta and gamma; comment those out and use descriptive names:

retain	daily	7
retain	weekly	4
retain	monthly	6

Enable the log file and make rsnapshot use rsync's --link-dest, which is faster than copying with cp -al before each run:

logfile	/var/log/rsnapshot.log
link_dest	1

Now go to the BACKUP POINTS / SCRIPTS section at the end of the file. Remove or comment out the example backup lines and add the local directories you want to protect. The third field is a subdirectory inside each snapshot, used to group backups by host:

backup	/etc/	localhost/
backup	/home/	localhost/
backup	/var/www/	localhost/

Exclude files that are large and easy to recreate. You can add exclude lines anywhere in the file; they apply to every backup point:

exclude	/home/*/.cache/
exclude	node_modules/
exclude	*.tmp

Save the file and check the syntax:

sudo rsnapshot configtest
Syntax OK

If you see an error such as ERROR: /etc/rsnapshot.conf on line 97: Backup directive must contain tab, open the file at that line and replace the spaces with tabs.

Step 3 - Running the first snapshot

Before running a real backup, use test mode (-t) to print the commands rsnapshot would execute without running them:

sudo rsnapshot -t daily
echo 4128 > /var/run/rsnapshot.pid
/usr/bin/rsync -a --delete --numeric-ids --relative --delete-excluded \
    --exclude=/home/*/.cache/ --exclude=node_modules/ --exclude=*.tmp \
    /etc/ /mnt/backup/snapshots/daily.0/localhost/
...

Now take the first snapshot. It copies all the data, so it takes the longest:

sudo rsnapshot daily

The command prints nothing on success. Check the log and the result:

sudo tail -n 2 /var/log/rsnapshot.log
sudo ls /mnt/backup/snapshots/daily.0/localhost/
[2026-09-25T10:12:03] /usr/bin/rsnapshot daily: started
[2026-09-25T10:14:47] /usr/bin/rsnapshot daily: completed successfully
etc  home  var

Run it a second time to see the rotation and the hard links at work:

sudo rsnapshot daily
sudo rsnapshot du
2.1G	/mnt/backup/snapshots/daily.0/
3.4M	/mnt/backup/snapshots/daily.1/
2.1G	total

The second snapshot adds only a few megabytes: unchanged files in daily.0 and daily.1 are the same inode on disk. You can confirm it for a single file, where both paths show the same inode number in the first column:

sudo ls -li /mnt/backup/snapshots/daily.{0,1}/localhost/etc/hostname

Step 4 - Backing up a remote server over SSH

rsnapshot can pull data from other servers with rsync over SSH. To read every file on web1, the connection must be made as root, so you will limit what the key can do with rrsync, a wrapper shipped with the rsync package that only allows rsync and, with -ro, only in read mode.

On the backup server, create a dedicated key without a passphrase (cron cannot type one):

sudo ssh-keygen -t ed25519 -f /root/.ssh/rsnapshot_ed25519 -N "" -C "rsnapshot@backup"
sudo cat /root/.ssh/rsnapshot_ed25519.pub

Copy the printed public key. Then, on web1, open root's authorized_keys:

sudo nano /root/.ssh/authorized_keys

Add one line with the restrictions followed by the key you copied:

command="/usr/bin/rrsync -ro /",restrict ssh-ed25519 AAAAC3Nza... rsnapshot@backup

restrict disables port forwarding, agent forwarding and TTY allocation, and command= forces every connection through rrsync in read-only mode. The key cannot open a shell or write files on web1. Ubuntu's default PermitRootLogin prohibit-password already allows key-only root logins.

Back on the backup server, connect once to accept the host key of web1:

sudo ssh -i /root/.ssh/rsnapshot_ed25519 root@web1_ip

Type yes to save the fingerprint. The connection closes immediately with an rrsync error, which is expected because you did not run rsync.

Now tell rsnapshot to use the key. In /etc/rsnapshot.conf, make sure cmd_ssh is uncommented and set ssh_args:

cmd_ssh	/usr/bin/ssh
ssh_args	-i /root/.ssh/rsnapshot_ed25519

Add the remote backup points, with a separate subdirectory for the host:

backup	root@web1_ip:/etc/	web1/
backup	root@web1_ip:/var/www/	web1/

Test the configuration and run a snapshot:

sudo rsnapshot configtest
sudo rsnapshot daily
sudo ls /mnt/backup/snapshots/daily.0/
localhost  web1

Step 5 - Including database dumps

Copying the data files of a running MySQL or PostgreSQL server does not produce a usable backup. Instead, let rsnapshot run a script that dumps the database and store the dump in the snapshot. rsnapshot runs backup_script entries in a temporary directory and moves any file the script writes there into the destination you name.

This example assumes MySQL or MariaDB runs on the backup server itself; on Ubuntu, root can connect through the Unix socket without a password. Create the script:

sudo nano /usr/local/bin/rsnapshot-mysqldump
#!/usr/bin/env bash
set -euo pipefail

# rsnapshot runs this in a temporary directory; write the dump there.
mysqldump --all-databases --single-transaction --routines --events --triggers \
  | gzip > all-databases.sql.gz

set -o pipefail makes the script fail if mysqldump fails, even though its output goes through gzip, and rsnapshot then reports the error. Make it executable and add it to /etc/rsnapshot.conf:

sudo chmod 750 /usr/local/bin/rsnapshot-mysqldump
backup_script	/usr/local/bin/rsnapshot-mysqldump	localhost/mysql/

After the next sudo rsnapshot daily, the dump appears at /mnt/backup/snapshots/daily.0/localhost/mysql/all-databases.sql.gz. For PostgreSQL, the same pattern works with runuser -u postgres -- pg_dumpall | gzip > all-databases.sql.gz.

Step 6 - Scheduling backups with cron

The Ubuntu package installs /etc/cron.d/rsnapshot with every line commented out. Open it:

sudo nano /etc/cron.d/rsnapshot

Replace its contents with one line per retention level:

# m  h  dom mon dow  user  command
30   2  1   *   *    root  /usr/bin/rsnapshot monthly
0    3  *   *   1    root  /usr/bin/rsnapshot weekly
30   3  *   *   *    root  /usr/bin/rsnapshot daily

The less frequent levels run first on purpose. monthly and weekly only rotate directories and finish in seconds, so they are done before daily starts syncing data. If two runs overlapped, the second one would stop at the lock file.

Files in /etc/cron.d are picked up automatically; there is no service to reload. The next morning, confirm that the run happened:

grep rsnapshot /var/log/syslog | tail -n 3
sudo tail -n 2 /var/log/rsnapshot.log

A line ending in completed successfully means the backup worked. With cron, a run that fails prints to standard error, and cron mails that output to root if a mail transfer agent is installed. On a server without one, check the log file regularly or have your monitoring system alert on lines containing ERROR.

Step 7 - Restoring files

Snapshots are plain directories, so restoring is a copy. First find which snapshots contain the file you need:

sudo ls -l /mnt/backup/snapshots/*/web1/var/www/html/index.php

Copy it back with rsync -a, which keeps permissions, ownership and timestamps. To restore a single file locally:

sudo rsync -a /mnt/backup/snapshots/daily.2/localhost/etc/nginx/nginx.conf /etc/nginx/nginx.conf

To restore a whole directory to a remote server, push it from the backup server with a normal SSH key (the rsnapshot key is read-only and will refuse writes):

sudo rsync -a /mnt/backup/snapshots/daily.0/web1/var/www/ root@web1_ip:/var/www/

Add --dry-run first to see which files would change. To restore a database dump:

sudo zcat /mnt/backup/snapshots/daily.0/localhost/mysql/all-databases.sql.gz | sudo mysql

Test a restore every few months on a scratch server. A backup you have never restored is an assumption, not a backup.

Troubleshooting

rsnapshot refuses to run because snapshot_root does not exist. This is no_create_root doing its job: the backup volume is not mounted, so rsnapshot will not write to the root disk. Check findmnt /mnt/backup and your /etc/fstab entry.

Lockfile /var/run/rsnapshot.pid exists and so does its process. A previous run is still going, usually because the daily sync takes longer than expected. Wait for it to finish. If the file is left over after a crash and no rsnapshot process is running (pgrep rsnapshot prints nothing), remove it with sudo rm /var/run/rsnapshot.pid.

rsync error: some files/attrs were not transferred (code 23). rsnapshot logs this as a warning and still completes the snapshot. It usually means files vanished during the copy or a path in a backup line does not exist. Run sudo rsnapshot -v daily to see which files are affected.

Permission denied (publickey) for remote backups. Check that ssh_args points to the right key, that the public key line in /root/.ssh/authorized_keys on the remote server is on a single line, and that the host key was accepted in Step 4.

Snapshots use much more space than expected. Hard links only work within one filesystem, and they break when a file changes in any way, including its permissions or timestamps. Log files and databases that change daily are stored in full each time; exclude them and back up dumps instead.

Conclusion

You now have rsnapshot keeping seven daily, four weekly and six monthly snapshots of local and remote data on a dedicated volume, with a consistent database dump and a restore procedure you have tested. As next steps, copy the snapshot volume to a second location to follow the 3-2-1 rule, for example with rsync to another data center or an object storage tool, and add an alert that fires when /var/log/rsnapshot.log has not recorded a successful run in the last 26 hours.