Skip to content
Go back

Postgres Backups: pg_dump vs pgBackRest vs WAL-G

By SumGuy 9 min read
Postgres Backups: pg_dump vs pgBackRest vs WAL-G

Your Postgres data is on fire, and you haven’t tested recovery

You know the feeling. It’s 2 AM. A query just deleted the wrong rows. Your app is screeching to a halt. You reach for that backup you set up three months ago and… nothing. You don’t actually know if it works.

Postgres backups aren’t as simple as “dump the database and call it a day.” Well, they can be. But if you want point-in-time recovery (PITR), 24-hour RPO, automated retention, and cloud-native handling of terabytes of data, you need to pick the right tool.

Here’s the thing: pg_dump is a hammer. pgBackRest is a precision drill. WAL-G is the robot arm. Each solves a different backup problem. Let’s walk through when you’d use each one, how to set them up, and—most importantly—how to know they actually work when you need them.


The Three Tools, Head-to-Head

pg_dump: The Hammer

pg_dump ships with Postgres. It’s the easiest backup tool you’ll ever use. It reads your entire database, converts it to SQL, and writes it to a file. Restore is simple: pipe it back into psql and you’re done.

When to use it:

When NOT to use it:

The real problem: pg_dump reads the entire database sequentially. On a busy production database, this hammers I/O and CPU. And if the dump is interrupted mid-backup, you lose the entire backup — it’s all-or-nothing.

pgBackRest: The Precision Drill

pgBackRest is the mature, production-grade Postgres backup tool. It creates full backups (WAL + filesystem snapshots) and then streams the Write-Ahead Log (WAL) to a repository. It’s been battle-tested at Postgres conferences for years.

When to use it:

When NOT to use it:

Why it’s good: pgBackRest uses Postgres’s backup API, not pg_dump. It’s resilient—if a backup is interrupted, pgBackRest picks up where it left off. WAL archiving is automatic. Retention policies are declarative. It’s actively maintained and widely used.

WAL-G: The Robot Arm

WAL-G is newer, cloud-native, and stupidly fast at scale. It was originally built by Citus Data and is now maintained primarily by Yandex for massive databases in the cloud. It streams WAL segments and full backups directly to S3 (or S3-compatible storage) with parallelized uploads.

When to use it:

When NOT to use it:

Why it’s good: WAL-G parallelizes uploads, compresses WAL, and integrates cleanly with cloud ecosystems. Restore is blazing fast because you’re not reading from slow NFS—you’re pulling from S3 in parallel. New features ship frequently.


Setup: Let’s Actually Build These

pg_dump Setup (5 minutes)

Terminal window
# Full backup to SQL file
pg_dump -U postgres -d myapp > myapp_backup.sql
# Compressed backup (saves ~70% space)
pg_dump -U postgres -d myapp | gzip > myapp_backup.sql.gz
# With custom format (compressed, faster restore)
pg_dump -U postgres -d myapp -Fc > myapp_backup.dump
# Backup all databases
pg_dumpall -U postgres | gzip > all_databases.sql.gz

That’s it. You now have a backup. Test restore:

Terminal window
# Restore from SQL
psql -U postgres -d myapp < myapp_backup.sql
# Restore from custom format (much faster for large DBs)
pg_restore -U postgres -d myapp myapp_backup.dump

Cron it:

Terminal window
0 2 * * * pg_dump -U postgres -d myapp -Fc | gzip > /backups/myapp_$(date +\%Y\%m\%d).dump.gz

Done. You have daily backups. No PITR. No streaming WAL. But it works.

pgBackRest Setup (30 minutes)

First, you need a repository—S3, MinIO, or NFS. Let’s use MinIO (self-hosted S3-compatible):

Terminal window
# Install pgBackRest
sudo apt-get install pgbackrest
# Create pgbackrest system user
sudo useradd -m -s /bin/bash pgbackrest
# Create repository directory (or use S3 bucket)
sudo mkdir -p /var/lib/pgbackrest
sudo chown pgbackrest:pgbackrest /var/lib/pgbackrest
sudo chmod 700 /var/lib/pgbackrest

Config file at /etc/pgbackrest/pgbackrest.conf:

[global]
repo1-type=s3
repo1-s3-bucket=postgres-backups
repo1-s3-endpoint=minio.local:9000
repo1-s3-region=us-east-1
repo1-s3-key=minioadmin
repo1-s3-key-secret=minioadmin
repo1-s3-uri-style=path
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=your-secret-passphrase
[postgres]
pg1-path=/var/lib/postgresql/16/main
pg1-user=postgres
pg1-port=5432
process-max=4
compress-type=bz2
retention-full=30

Initialize the repo:

Terminal window
sudo -u pgbackrest pgbackrest --stanza=postgres repo-create

Test connectivity:

Terminal window
sudo -u pgbackrest pgbackrest --stanza=postgres check

Create a full backup:

Terminal window
sudo -u pgbackrest pgbackrest --stanza=postgres backup --type=full

WAL archiving is now automatic. pgBackRest watches /var/lib/postgresql/16/main/pg_wal/ and ships segments to your S3/MinIO repo.

Cron it:

Terminal window
# Daily incremental backup (only changed blocks since last full)
0 3 * * * sudo -u pgbackrest pgbackrest --stanza=postgres backup --type=diff
# Weekly full backup
0 4 * * 0 sudo -u pgbackrest pgbackrest --stanza=postgres backup --type=full

List backups:

Terminal window
sudo -u pgbackrest pgbackrest --stanza=postgres info

WAL-G Setup (20 minutes)

Install WAL-G:

Terminal window
# Download latest binary (check releases on GitHub)
wget https://github.com/wal-g/wal-g/releases/download/v3.0.0/wal-g-pg.linux-amd64.tar.gz
tar xzf wal-g-pg.linux-amd64.tar.gz
sudo mv wal-g-pg /usr/local/bin/wal-g
sudo chmod +x /usr/local/bin/wal-g

Set up S3 credentials in a systemd environment file /etc/default/wal-g:

Terminal window
export PGDATA=/var/lib/postgresql/16/main
export AWS_ACCESS_KEY_ID=minioadmin
export AWS_SECRET_ACCESS_KEY=minioadmin
export AWS_S3_FORCE_PATH_STYLE=true
export WALE_S3_ENDPOINT=https://minio.local:9000
export WALG_S3_PREFIX=s3://postgres-backups/wal-g
export WALG_COMPRESSION_METHOD=brotli
export WALG_DELTA_MAX_STEPS=5
export WALG_DOWNLOAD_CONCURRENCY=4
export WALG_UPLOAD_CONCURRENCY=4

Configure Postgres for WAL-G archiving. In postgresql.conf:

wal_level = replica
max_wal_senders = 3
max_replication_slots = 1
archive_mode = on
archive_command = '. /etc/default/wal-g && wal-g wal-push %p'
archive_timeout = 300

Restart Postgres:

Terminal window
sudo systemctl restart postgresql

Create a full backup:

Terminal window
. /etc/default/wal-g && wal-g backup-push /var/lib/postgresql/16/main

WAL segments now stream automatically. Check backups:

Terminal window
. /etc/default/wal-g && wal-g backup-list

Point-in-Time Recovery (PITR)

This is where pgBackRest and WAL-G shine.

Say your table got nuked at 14:47 UTC. You want to recover to 14:45 UTC.

With pgBackRest

Terminal window
# List backups
sudo -u pgbackrest pgbackrest --stanza=postgres info
# Stop Postgres
sudo systemctl stop postgresql
# Restore to specific point in time
sudo -u pgbackrest pgbackrest --stanza=postgres restore \
--type=time \
--target='2026-05-09 14:45:00+00' \
--restore-path=/var/lib/postgresql/16/main
# Change ownership
sudo chown postgres:postgres /var/lib/postgresql/16/main -R
# Start Postgres
sudo systemctl start postgresql

Done. Your database is at 14:45 UTC, with all WAL segments replayed up to that point.

With WAL-G

Terminal window
# Stop Postgres
sudo systemctl stop postgresql
# Restore from S3 to specific time
. /etc/default/wal-g && wal-g backup-fetch /var/lib/postgresql/16/main LATEST
# Restore to point-in-time
. /etc/default/wal-g && wal-g wal-fetch /var/lib/postgresql/16/main --target-time='2026-05-09 14:45:00+00'
# Change ownership and permissions
sudo chown postgres:postgres /var/lib/postgresql/16/main -R
sudo chmod 700 /var/lib/postgresql/16/main
# Start Postgres
sudo systemctl start postgresql

Same result, slightly different commands.


Retention Policies (So You Don’t Fill Your Disk)

pgBackRest Retention

In /etc/pgbackrest/pgbackrest.conf:

[global]
retention-full=30 # Keep 30 full backups
retention-diff=20 # Keep 20 diff backups
retention-archive=90 # Keep 90 days of WAL

pgBackRest automatically expires old backups and WAL when you run:

Terminal window
sudo -u pgbackrest pgbackrest --stanza=postgres expire

(This is usually cron’d daily.)

WAL-G Retention

WAL-G has less built-in retention logic. You manage it manually:

Terminal window
# List backups older than 30 days
. /etc/default/wal-g && wal-g delete before LATEST --confirm
# Delete specific backup
. /etc/default/wal-g && wal-g delete <backup-name> --confirm

Add a cron job:

Terminal window
0 5 * * * . /etc/default/wal-g && wal-g delete before $(date -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ) --confirm

The Restore Drill (Do This Today)

Reading about backups is easy. Actually restoring is where everything breaks.

Here’s your drill:

  1. Take a backup right now

    Terminal window
    sudo -u pgbackrest pgbackrest --stanza=postgres backup --type=full
  2. Wait 10 minutes. Let some WAL segments accumulate.

  3. Do something intentional (so you can verify recovery)

    Terminal window
    psql -U postgres -d myapp -c "INSERT INTO users (name) VALUES ('test-restore');"
  4. Recover to 5 minutes ago

    Terminal window
    sudo systemctl stop postgresql
    sudo -u pgbackrest pgbackrest --stanza=postgres restore \
    --type=time \
    --target='now -5 min' \
    --restore-path=/var/lib/postgresql/16/main
    sudo chown postgres:postgres /var/lib/postgresql/16/main -R
    sudo systemctl start postgresql
  5. Verify the row is gone

    Terminal window
    psql -U postgres -d myapp -c "SELECT COUNT(*) FROM users WHERE name = 'test-restore';"

If that row count is 0, your backup works. Now document the recovery procedure and add it to your runbook.


Decision Tree: Which One Do You Pick?

Use pg_dump if:

Use pgBackRest if:

Use WAL-G if:

Use all three if:


The Thing Nobody Tells You

Backups don’t matter. Restores matter. A backup you’ve never tested is just data taking up space. Every backup system on this page will sit untested until the moment you need it—at 2 AM, with your boss watching.

So set it up, test recovery today, and add the procedure to your runbook. Then you can sleep through the night.

Your 2 AM self will thank you.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Boundary vs Teleport

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts