Building a Real Project: Automated Backup Script

Combine everything from this course into one working script that backs up a folder, compresses it, timestamps it, logs its work and is ready to schedule.

~10 min Beginner

What we are building

This project combines what you have learned into one working tool. The script will:

  • take a folder to back up as an argument,
  • save a compressed .tar.gz archive with the date in its name,
  • write messages to a log file,
  • delete backups older than 7 days,
  • be ready to run from cron.

The steps are: check the input, create the archive, log the result, then delete old backups. Each step below adds one part of the script.

Step 1: settings

#!/bin/bash
# backup.sh - back up a folder to a compressed archive

SOURCE="$1"
DEST="$HOME/backups"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M-%S)
LOG="$DEST/backup.log"

$1 is the folder given on the command line. Putting settings at the top means you change them in one place. STAMP holds the current date and time, for example 2026-10-05_02-00-01.

Step 2: check the input

if [ $# -ne 1 ]; then
    echo "Usage: $0 <folder-to-back-up>"
    exit 1
fi

if [ ! -d "$SOURCE" ]; then
    echo "Error: $SOURCE is not a folder"
    exit 2
fi

mkdir -p "$DEST"

The first test checks that exactly one argument was given. The second checks that it is a directory, because tar would produce a confusing error for a missing or wrong path. The script stops early with a clear message and a different exit code for each problem. Finally mkdir -p makes sure the destination folder exists.

Step 3: a logging function

log() {
    echo "[$(date '+%F %T')] $1" | tee -a "$LOG"
}

The function adds a time stamp to a message. tee -a prints it to the screen and appends it to the log file. From now on the script calls log instead of echo. Define this function before it is first used.

Step 4: create the archive

NAME=$(basename "$SOURCE")
ARCHIVE="$DEST/${NAME}_$STAMP.tar.gz"

log "Starting backup of $SOURCE"

if tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$NAME"; then
    SIZE=$(du -h "$ARCHIVE" | cut -f1)
    log "Backup created: $ARCHIVE ($SIZE)"
else
    log "ERROR: backup failed"
    exit 3
fi
  • basename gives the last part of the path, so /home/alex/Documents becomes Documents. dirname gives the part before it.
  • tar -czf means create an archive, compress it with gzip, and write it to the file named next.
  • -C tells tar to change to that folder first, so the archive holds Documents/... and not the full path.
  • The if runs tar directly. If tar succeeds (exit status 0) the first block runs, otherwise the else block logs the failure and exits.
  • du -h reports the archive size and cut -f1 keeps only the size column.

Step 5: clean up old backups

log "Removing backups older than $KEEP_DAYS days"
find "$DEST" -name "${NAME}_*.tar.gz" -mtime +"$KEEP_DAYS" -delete
log "Done"

-mtime +7 matches files modified more than 7 days ago. The name pattern limits the deletion to backups of this folder, so nothing else in $DEST is touched.

The complete script

#!/bin/bash
# backup.sh - back up a folder to a compressed archive

SOURCE="$1"
DEST="$HOME/backups"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M-%S)
LOG="$DEST/backup.log"

log() {
    echo "[$(date '+%F %T')] $1" | tee -a "$LOG"
}

if [ $# -ne 1 ]; then
    echo "Usage: $0 <folder-to-back-up>"
    exit 1
fi

if [ ! -d "$SOURCE" ]; then
    echo "Error: $SOURCE is not a folder"
    exit 2
fi

mkdir -p "$DEST"

NAME=$(basename "$SOURCE")
ARCHIVE="$DEST/${NAME}_$STAMP.tar.gz"

log "Starting backup of $SOURCE"

if tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$NAME"; then
    SIZE=$(du -h "$ARCHIVE" | cut -f1)
    log "Backup created: $ARCHIVE ($SIZE)"
else
    log "ERROR: backup failed"
    exit 3
fi

log "Removing backups older than $KEEP_DAYS days"
find "$DEST" -name "${NAME}_*.tar.gz" -mtime +"$KEEP_DAYS" -delete
log "Done"

Run it and schedule it

chmod +x backup.sh
./backup.sh ~/Documents
ls -lh ~/backups
tar -tzf ~/backups/Documents_*.tar.gz | head

tar -tzf lists the contents of an archive without extracting it, so head shows the first few entries.

To run the backup every night at 2 AM, add this to crontab -e. Use full paths because cron has a small environment:

0 2 * * * /home/alex/backup.sh /home/alex/Documents >> /home/alex/backup-cron.log 2>&1

Test the restore

A backup you have never restored is untested. Extract one into a temporary folder and check that the files are there:

mkdir -p /tmp/restore-test
tar -xzf ~/backups/Documents_*.tar.gz -C /tmp/restore-test
ls /tmp/restore-test

-x extracts, -z handles gzip and -f names the file. -C chooses where to extract.

Ideas to extend it

  • Back up several folders by looping over all arguments.
  • Add a --dry-run flag that prints what it would do.
  • Exclude folders with tar --exclude=node_modules.
  • Copy the archive to another machine with scp or rsync.
  • Send yourself a message if the backup fails.

Do not add steps that delete the source folder. A backup script should never remove the data it protects.

Sign up to track your progress Mark lessons as read and pick up where you left off Sign up free to unlock this practice Your first practice lesson is free to try. Create an account to unlock the rest and save your progress