Backups & Data·7 min read·

Automating PostgreSQL Backups with pg_dump and Cron

A clean cron-driven pipeline for PostgreSQL: automated pg_dump archives, per-database isolation, retention pruning, and a simple integrity audit.

NB

Netbay Developer Relations

Netbay Engineering

On this page

PostgreSQL gives you two ways to get data out of a server, and for daily operations you mostly need one: pg_dump. It is logical, portable across server versions, and safe to run on a busy VPS without stopping traffic. The part everyone learns the hard way is the automation around it. A dump sitting on the same disk as the database is a false sense of safety. The pipeline from dump, to off-site archive, to verified restore is the actual backup.

Choose the right pg_dump format

Plain SQL dumps are fine for small databases, but the custom format earns its keep quickly. Custom-format archives are compressed, support selective restore with pg_restore, and let you apply restores in parallel. Plain text is easier to grep; custom is easier to live with.

A solid single-database command looks like this:

bash
pg_dump --format=custom --compress=9 --no-owner --no-privileges   --quote-all-identifiers --file=/var/backups/postgres/appdb.dump   "postgres://backup_user@10.0.0.4/appdb"

--no-owner and --no-privileges stop the dump from pinning roles that exist only on the source host, which matters when the restore target is a fresh machine. --quote-all-identifiers keeps the dump round-trippable even with unusual column names.

One dump per database, not one guess

Hardcoding a single database name means every other database silently loses its backup. Loop over the list the server actually reports and build one archive per database, stamped with the date.

bash
#!/usr/bin/env bash
# /usr/local/bin/pg_dump_all.sh
set -euo pipefail
BACKUP_DIR=/var/backups/postgres
RETENTION_DAYS=30
stamp=$(date +%Y%m%d)
mkdir -p $BACKUP_DIR
for db in $(psql -At -c "SELECT datname FROM pg_database WHERE datistemplate = false;" postgres); do
  pg_dump --format=custom --compress=9 --no-owner --no-privileges     --quote-all-identifiers     --file=$BACKUP_DIR/$db.$stamp.dump     "postgres://backup_user@10.0.0.4/$db"
done
find $BACKUP_DIR -name '*.dump' -mtime +$RETENTION_DAYS -delete
ls -lh $BACKUP_DIR | tail -n +2

The query filters out template databases, so you never attempt a dump of an internal structure.

Cron, with a smoke test attached

A cron entry that runs a script is one line. A cron entry that quietly writes a zero-byte dump for three weeks is how disasters become famous. Add a post-run smoke test that fails loudly when the newest archive cannot be read back.

bash
# /etc/cron.d/pg-backup
30 1 * * * root /usr/local/bin/pg_dump_all.sh
15 6 * * * root /usr/local/bin/pg_backup_audit.sh
bash
#!/usr/bin/env bash
# /usr/local/bin/pg_backup_audit.sh
set -euo pipefail
latest=$(ls -t /var/backups/postgres/appdb.*.dump | head -1)
pg_restore --list $latest >/dev/null 2>&1   || { echo "audit failed: $latest unreadable"; exit 1; }
echo "audit ok: $(basename $latest)"

The audit runs a few hours after the dump finishes, which leaves time for transient hiccups to surface before you rely on the file.

postgres backup pipeline pg_dump -Fc per database cron 01:30 /etc/cron.d /var/backups 30-day retention off-site + audit pg_restore --list failed audit exits non-zero, cron mail fires rsync + restic forget verify in weekly drill

The last mile: off-site and retention

None of this matters if the only copy lives beside the database. Push the archive directory off the box after each dump, and keep pruning honest:

bash
# appended to pg_dump_all.sh after the dump loop
rsync -a --delete /var/backups/postgres/ offsite:backups/postgres/
restic -r sftp:offsite:/backups backup /var/backups/postgres
restic -r sftp:offsite:/backups forget --keep-daily 30 --prune

Retention is a policy, so express it as one: 30 nightly copies pruned on every run, locally and off-site identically. That way the restore target never has to scan a mountain of old files to find today's archive.

Takeaway

pg_dump is dependable, but automation turns it into a backup system: per-database archives, cron with an independent smoke test, off-site copies, and retention that prunes on schedule. Practice one restore today rather than after the disk fails. When you want a clean Ubuntu 24.04 host to stage this pipeline, netbayhosts.in provisions one in under a minute.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance