Skip to content
Commitpedia
Log in Join & edit

verify-latest-backup: alert when the newest dump is too old or too small

A read-only bash check that finds the newest file in a backup directory, compares its age and size against limits, optionally tests the compressed archive, and exits non-zero so a timer or monitoring can alert.

Drafted with AI from official documentation. Scripts were run in a test environment and repository links verified. Awaiting a human check. Did it work for you? Last edited Outdated?

Edit history of verify-latest-backup: alert when the newest dump is too old or too small: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
bash
Licence
CC0-1.0

What this changes

Nothing. The script only reads directory listings and, for .gz or .zst files, runs the archive's integrity test, which reads the file without writing. It prints a status line and returns exit code 0, 2, 3, 4 or 5.

How to undo it

There is nothing to undo; the script makes no changes to any file or system state.

Most backup failures are quiet: the job stops running, or it keeps running and produces a 20-byte file with an error message inside. A monitoring check that looks at the result, not the job, catches both. This script answers two questions about a directory of dumps: is the newest file recent enough, and is it big enough to plausibly contain data?

The script

Save as /usr/local/bin/verify-latest-backup.sh and make it executable. It needs GNU find (standard on Linux) for the -printf option.

#!/usr/bin/env bash
set -euo pipefail

usage() {
  echo "usage: $0 -d DIR [-p PATTERN] [-a MAX_AGE_HOURS] [-s MIN_SIZE_BYTES]"
  echo "exit codes: 0 ok, 2 no matching file, 3 newest file too old, 4 newest file too small, 5 archive integrity test failed"
}

DIR=""
PATTERN="*"
MAX_AGE_HOURS=26
MIN_SIZE=1024

while getopts ":d:p:a:s:h" opt; do
  case "$opt" in
    d) DIR="$OPTARG" ;;
    p) PATTERN="$OPTARG" ;;
    a) MAX_AGE_HOURS="$OPTARG" ;;
    s) MIN_SIZE="$OPTARG" ;;
    h) usage; exit 0 ;;
    *) usage; exit 1 ;;
  esac
done

if [ -z "$DIR" ] || [ ! -d "$DIR" ]; then
  usage
  exit 1
fi

newest=$(find "$DIR" -maxdepth 1 -type f -name "$PATTERN" -printf '%T@ %s %p\n' | sort -n | tail -n 1)
if [ -z "$newest" ]; then
  echo "CRITICAL: no file matching '$PATTERN' in $DIR"
  exit 2
fi

mtime=${newest%% *}
rest=${newest#* }
size=${rest%% *}
file=${rest#* }
mtime=${mtime%.*}
now=$(date +%s)
age_hours=$(( (now - mtime) / 3600 ))

echo "newest: $file"
echo "age:    ${age_hours}h (limit ${MAX_AGE_HOURS}h)"
echo "size:   ${size} bytes (minimum ${MIN_SIZE})"

if [ "$age_hours" -gt "$MAX_AGE_HOURS" ]; then
  echo "CRITICAL: newest backup is older than ${MAX_AGE_HOURS} hours"
  exit 3
fi

if [ "$size" -lt "$MIN_SIZE" ]; then
  echo "CRITICAL: newest backup is smaller than ${MIN_SIZE} bytes"
  exit 4
fi

case "$file" in
  *.gz)
    if ! gzip -t "$file"; then
      echo "CRITICAL: gzip integrity test failed for $file"
      exit 5
    fi
    ;;
  *.zst)
    if command -v zstd >/dev/null && ! zstd -t -q "$file"; then
      echo "CRITICAL: zstd integrity test failed for $file"
      exit 5
    fi
    ;;
esac

echo "OK: latest backup looks healthy"
exit 0

Usage

verify-latest-backup.sh -d /var/backups/db/postgresql -p 'app_*.dump' -a 26 -s 1000000
verify-latest-backup.sh -d /var/backups/db/mysql -p '*.sql.gz' -a 26 -s 50000
echo "exit code: $?"
  • -d is the directory, -p a shell glob for the file names (quote it so your shell does not expand it), -a the maximum age in hours and -s the minimum size in bytes.
  • A 26-hour limit for a nightly job leaves room for a slow run or a delayed start without masking a missed night.
  • Set the minimum size to roughly half of a normal dump. If your dumps are usually 80 MB and today’s is 2 KB, the database was probably unreachable and the file contains an error message.
  • Exit codes are distinct so a wrapper can tell the cases apart: 2 nothing found, 3 too old, 4 too small, 5 the archive fails its integrity test.

Wire it to an alert

Run it from a systemd timer an hour after the backup job. A non-zero exit puts the unit into the failed state, which shows up in systemctl --failed, in the Server health check script and in any monitoring agent that watches systemd units. To send an email or chat message, add an OnFailure= line to the service pointing at a notify unit:

[Unit]
Description=Verify newest PostgreSQL dump
OnFailure=notify-failure@%n.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/verify-latest-backup.sh -d /var/backups/db/postgresql -p '*.dump' -a 26 -s 1000000

For Prometheus, run the script from a wrapper that writes a backup_check_status metric into the node_exporter textfile collector directory, and alert when the value is not 0 for more than an hour.

What this does not prove

A file that is fresh, large and decompresses cleanly can still be a dump of the wrong database, or a dump that will not import because of a version mismatch. This check is the daily smoke test; the monthly restore drill is the real proof. Use both.

Official documentation

Did this work for you?

No votes yet. Be the first.

If it did not work, the report form below tells the editors what went wrong.

See also

  • 0059 Incremental snapshot backups with rsync A bash script that copies a folder into dated snapshots with rsync, hard-links unchanged files to the previous snapshot to save space,… Advanced · Step by step · Linux, macOS Script Backups & syncing · Shell scripting 15 Oct 2026
  • 0308 What is a backup snapshot? A snapshot is a frozen picture of a disk or system at one moment that you can roll back to if something… Beginner · Explains an idea · Any system Article Strategy & restore drills 15 Oct 2026
  • 0362 The 3-2-1 backup rule, and why a backup you never restored is not a backup Three copies, two kinds of media, one off-site: what the rule really protects against, how it maps to real tools, and why… Beginner · Explains an idea · Any system Article Strategy & restore drills 15 Oct 2026
  • 0363 Restore drill: prove your backup works in 15 minutes A repeatable procedure to restore one file and one database from your newest backup into a scratch location, compare the result with… Intermediate · Step by step · Linux, macOS Article Backup tools · Strategy & restore drills 15 Oct 2026
  • 0366 restic backup wrapper with tagging, retention and a weekly check A bash wrapper that runs restic backup on a fixed list of paths, applies a keep-daily/weekly/monthly policy with forget and prune, runs… Intermediate · Step by step · Linux, macOS Script Backup tools · Backups & syncing 15 Oct 2026

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. Dominik +4,503 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?