Archive PostgreSQL WAL Safely with pg_receivewal
A replication slot without a live receiver behind it is a disk-filling time bomb, and pg_receivewal is the tool that keeps the promise the slot makes. This guide sets up a controlled stream of PostgreSQL write-ahead log (WAL) files into a local archive directory, using an existing physical replication slot and a receiver that reports durable progress. The examples match the PostgreSQL 16.15 client installed here.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about 20 minutes for the client-side checks, plus whatever time your database administrator needs to approve the replication user, pg_hba.conf rule and slot.
- You need: PostgreSQL client utilities, a reachable PostgreSQL server, a user with the
REPLICATIONprivilege or superuser access, and enough free space for the archive. - The server needs: at least one available WAL sender.
Warning
This is a backup component, not a complete point-in-time recovery plan. Test restoring from the received files before relying on them. A replication slot protects WAL from being recycled, but a receiver that stops can eventually fill the primary server's disk.
1. Confirm the installed client
Start with read-only checks. They do not need elevated privileges:
$ pg_receivewal --version
pg_receivewal (PostgreSQL) 16.15
$ command -v pg_receivewal
/usr/bin/pg_receivewal
The installed package here is postgresql-client-16, version 16.15-0ubuntu0.24.04.1. Your package revision and binary path may differ. Keep the full option names in scripts so a future reader can see what each value controls.
Checkpoint
pg_receivewal --help shows --directory, --slot, --synchronous and --no-loop. The output directory is mandatory.
2. Prepare an archive directory
Choose a directory on storage with a suitable retention and monitoring policy. This example creates a new directory below /var/backups, so it normally requires elevated privileges:
$ sudo install -d -o postgres -g postgres -m 0750 /var/backups/postgresql/wal
$ sudo -u postgres test -w /var/backups/postgresql/wal && echo writable
writable
- Use the account that will run the receiver. The directory must be writable by that account.
- Do not reuse a data directory. It must not be a PostgreSQL data directory or a directory already containing unrelated WAL files.
- Testing as yourself? Use a directory under your home directory instead and omit
sudo.
This step changes filesystem state. To undo the empty test directory, first confirm its contents are disposable, then remove only that exact directory with sudo rmdir /var/backups/postgresql/wal. Do not remove an archive directory containing WAL you have not backed up elsewhere.
3. Check the server-side prerequisites
Ask the database administrator to verify these items before starting a long-running receiver:
- Connection allowed.
pg_hba.confpermits this as a replication connection. - Privilege granted. The login has
REPLICATIONprivilege or is a superuser. - Sender capacity free.
max_wal_sendersleaves a session available for this receiver. - Slot ready. The named physical replication slot already exists, or its creation has been approved.
Use a distinct slot name such as backup_wal_01. Creating a slot retains WAL on the server and therefore changes server state. If you must create one, review the retention risk first:
$ pg_receivewal --dbname='host=DB_HOST port=5432 user=wal_receiver' \
--slot=backup_wal_01 --create-slot
A successful create-slot action exits without starting the stream. If the slot already exists, the command fails unless you add --if-not-exists. That option makes setup idempotent, but it does not prove the existing slot has the name, type or ownership you intended.
To undo a slot you created, stop every receiver using it and ask the administrator to check that no backup still depends on it, then run:
$ pg_receivewal --dbname='host=DB_HOST port=5432 user=wal_receiver' \
--slot=backup_wal_01 --drop-slot
Warning
Dropping a slot can allow the server to remove WAL that was being retained for this receiver. Treat it as irreversible for the backup chain.
4. Start a normal WAL stream
Use the existing slot and a password method approved for your environment. A .pgpass entry is usually safer for an unattended service than a password on the command line. The database name in --dbname is ignored by this utility, but connection parameters in a connection string override conflicting command-line values.
$ pg_receivewal \
--host=DB_HOST \
--port=5432 \
--username=wal_receiver \
--directory=/var/backups/postgresql/wal \
--slot=backup_wal_01 \
--verbose
The process runs continuously, writing WAL as the server generates it, and normally reconnects indefinitely after a non-fatal connection failure. Stop it with Control+C or SIGTERM; either signal produces normal exit status 0. Do not use --no-loop unless your supervisor should handle retries itself.
In a second terminal, check that files are appearing without changing them:
$ find /var/backups/postgresql/wal -maxdepth 1 -type f -printf '%f %s bytes\n' | sort | tail
$ df -h /var/backups/postgresql/wal
WAL generation is workload-dependent, so an empty directory immediately after startup is not proof of failure. Verbose output and the receiver's exit status matter more than waiting for a particular filename.
5. Choose the flush and feedback behaviour deliberately
By default, pg_receivewal flushes received WAL when a WAL file closes. Add --synchronous when you need each received write flushed promptly and an immediate status packet sent after the flush:
$ pg_receivewal \
--host=DB_HOST --port=5432 --username=wal_receiver \
--directory=/var/backups/postgresql/wal \
--slot=backup_wal_01 --synchronous --verbose
This option is also required if the receiver is deliberately configured as a synchronous standby and must give timely feedback.
Warning
Do not accidentally put this receiver in a configuration where synchronous_commit=remote_apply waits for it. pg_receivewal stores WAL but does not apply it, so commits can block behind a receiver that never catches up. Use a non-matching application name or an appropriate synchronous-standby configuration.
Avoid --no-sync for production archiving. It can be faster, but an operating system crash can leave received WAL corrupt, and the option cannot be combined with --synchronous.
6. Test a bounded run and common failures
For a supervised smoke test, --endpos stops normally once the receiver reaches the specified LSN. The LSN must come from your PostgreSQL backup procedure; do not invent one:
$ pg_receivewal --host=DB_HOST --port=5432 --username=wal_receiver \
--directory=/var/backups/postgresql/wal --slot=backup_wal_01 \
--endpos=START_LSN --verbose
$ printf 'exit status: %s\n' "$?"
exit status: 0
- Connection error causing a retry loop? Rerun the diagnostic command with
--no-loop. - Unexpected password prompt? Expected when the server requests password authentication; use
--no-passwordin a non-interactive check if prompting would hang a job. - Missing directory, unwritable destination, invalid slot or rejected connection? That is a configuration error, not a reason to add
sudoblindly.
Check disk usage on both sides. The archive destination can fill locally, while a stalled slot can fill the primary server. Set alerts before turning this into a service. If the receiver must be replaced, stop the old process cleanly, confirm the new process uses the same slot and directory, and keep the old files in place.
Done means
- Client confirmed. The installed PostgreSQL client version and output directory are known.
- Server side checked. A replication user,
pg_hba.confrule, WAL sender capacity and physical slot are verified by the administrator. - Receiver running correctly. It writes to a dedicated directory and its reconnect behaviour is understood.
- Durability decided. Flush behaviour matches the backup and synchronous-standby design;
--no-syncis not used for production archiving. - Disk monitored. Usage is watched on both the archive host and the primary server.
- Restore tested. A bounded test or clean shutdown returned status 0, and the received files are part of a tested restore process.