Backup Routine with cron, tar, and nfs
Introduction
My backup setup makes use of NFS (Network File System) to mount the filesystem of the source machine on that of the destination machine, tar to carry out the full and incremental backups, and cron to schedule the backups daily. It's a simple setup, but it could be simpler by using an external HDD to store the backups rather than a whole additional system. Hey, I'm just using what I have.
Both machines I have were acquired from a business once they outlived their usefulness. One was used as a desktop that ran Windows 7, and the other was used as a server. Evidently, in both cases it made more sense to sunset the machines than to upgrade their operating systems and hardware. Running Linux, they perform very nicely.
One machine is my server, which I use for file storage and jellyfin (read about that here). The other machine currently only runs the backup routine and stores the backups. I'll find other uses for it in the future. They are connected to one another via a cat5e ethernet cable: the hardware is recent enough that a crossover cable is not required. The server has a Wi-Fi adapter whereas the backup machine doesn't, so Internet is shared between them via ethernet.
Finally, the filesystem of each machine is mounted on the other via NFS via ethernet.
nfs
tar
While searching for the tools to use for this project, I stumbled upon the GNU tar manual backups section.[1] Of course, I use tar all the time, but I didn't know it was fully equipped to handle backups as well. I made use of these subsections:
- 5.1 Using
tarto Perform Full Dumps - 5.2 Using
tarto Perform Incremental Dumps - 5.3 Levels of Backups
The next two subsections, 5.4 and 5.5, discuss how to use the built-in backup and restore scripts. No doubt, that is easier than what I did. I wanted an excuse to write a bash script, so I decided to create those scripts myself. Here's the script I wrote.
#!/bin/bash
# AUTHOR: Sean David Reed
# DATE: 2026-02-10
# LICENSE: GPL-3+
#
# Used in conjunction with /etc/cron.d/my_backup_routine
# Cronjob runs at 01:00AM:
# 0 1 * * * sean /path/to/file/backup.sh
# Initialize variables
dir="/home/sean/backups"
target="/home/sean/sean-server/home/sean"
number=$(( (`date +%u` + 6) % 7 ))
snapshot="backup.snar-${number}"
archive="archive.${number}.tar"
# If it's Monday (0), stash old full dump and snapshot.
# Else create duplicate snapshot file.
if [[ $number -eq 0 ]]; then
mv ${dir}/${snapshot} ${dir}/old_${snapshot}
mv ${dir}/${archive} ${dir}/old_${archive}
else
cp ${dir}/backup.snar-$((number - 1)) ${dir}/${snapshot}
fi
# Perform full dump (Level 0) on Mondays (0)
# and incremental dump (Level 1) otherwise (1-6).
tar --create \
--file=${dir}/archive.${number}.tar \
--listed-incremental=${dir}/${snapshot} \
${target} 2>&1 | logger -t backup${number}
# If full dump is successful, destroy old backups and snapshots.
if [[ $? -eq 0 && -f ${dir}/old_backup.snar-0 ]]; then
rm ${dir}/old_backup.snar-0
rm ${dir}/old_archive.0.tar
for (( i = 1; i < 7; i++ )); do
rm ${dir}/archive.${i}.tar
rm ${dir}/backup.snar-${i}
done
# If full dump is unsuccessful, restore old full dump and snapshot.
# (Returns backups folder to state prior to running this script).
elif [[ -f ${dir}/old_backup.snar-0 ]]; then
CODE=$?
mv ${dir}/old_backup.snar-0 ${dir}/backup.snar-0
mv ${dir}/old_archive.0.tar ${dir}/archive.0.tar
exit $CODE
fi
Script - Step by Step
# Initialize variables
dir="/home/sean/backups"
target="/home/sean/sean-server/home/sean"
number=$(( (`date +%u` + 6) % 7 ))
snapshot="backup.snar-${number}"
archive="archive.${number}.tar"
$dir is simply the directory where everything pertaining to the backup process will live (except the cronjob).
$target is the directory to back up.
$number is the number of backup: my routine performs a full backup every Monday at 1:00AM, and an incremental backup every other day of the week before restarting the whole process again on Monday. Because Monday is the start of my backup cycle and date +%u designates Sunday as 0, I used a little modular arithmetic to shift each number over. Monday becomes 0, Tuesday becomes 1, and Sunday rotates back to 6.
$snapshot names the snapshot archive (.snar) according to the current backup number.
$archive names the tape archive (.tar) according to the current backup number
# If it's Monday (0), stash old full dump and snapshot.
# Else create duplicate snapshot file.
if [[ $number -eq 0 ]]; then
mv ${dir}/${snapshot} ${dir}/old_${snapshot}
mv ${dir}/${archive} ${dir}/old_${archive}
else
cp ${dir}/backup.snar-$((number - 1)) ${dir}/${snapshot}
fi
If it's Monday, the start of the backup cycle, stash the old Monday .tar and .snar files in case the new full backup fails. Otherwise, copy over the most recent snapshot, renaming it to the current backup. Every backup needs a corresponding snapshot.
# Perform full dump (Level 0) on Mondays (0)
# and incremental dump (Level 1) otherwise (1-6).
tar --create \
--file=${dir}/archive.${number}.tar \
--listed-incremental=${dir}/${snapshot} \
${target} 2>&1 | logger -t backup${number}
Here's all the heavy lifting. The format of this command with all its arguments is laid out in the GNU tar manual backup subsection 5.2. I use my variables to tidy things up, and the end of the last line bears scrutiny: 2>&1 | logger backup${number}. Thanks to a user at Ask Ubuntu,[2] I was able to resolve an error that tar was printing to STDERR, namely that no MTA (Mail Transfer Agent) was installed to handle logging. [EXPLAIN 2>&1]
# If full dump is successful, destroy old backups and snapshots.
if [[ $? -eq 0 && -f ${dir}/old_backup.snar-0 ]]; then
rm ${dir}/old_backup.snar-0
rm ${dir}/old_archive.0.tar
for (( i = 1; i < 7; i++ )); do
rm ${dir}/archive.${i}.tar
rm ${dir}/backup.snar-${i}
done
[EXPLAIN, EXPLAIN, EXPLAIN]
# If full dump is unsuccessful, restore old full dump and snapshot.
# (Returns backups folder to state prior to running this script).
elif [[ -f ${dir}/old_backup.snar-0 ]]; then
CODE=$?
mv ${dir}/old_backup.snar-0 ${dir}/backup.snar-0
mv ${dir}/old_archive.0.tar ${dir}/archive.0.tar
exit $CODE
fi
[EXPLAIN, EXPLAIN, EXPLAIN]
cron
This part is straightforward. Navigate to /etc/cron.d and create a new file.
cd /etc/cron.d
sudo vi my_backup_routine
This is alls ya need.
0 1 * * * sean /path/to/dir/backups/backup.sh
After that, restart the cron service.
sudo systemctl restart cron





