Archive PostgreSQL WAL Safely with pg_receivewal
You will finish with a controlled way to stream 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 REPLICATION privilege or superuser access, and enough free space for the archive. The server also 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 is postgresql-client-16, version 16.15-0ubuntu0.24.04.1 on this machine. 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
Confirm that 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, and it must not be a PostgreSQL data directory or a directory already containing unrelated WAL files. If you are testing as your own user, 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 that 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:
- The connection is allowed by
pg_hba.confas a replication connection. - The login has
REPLICATIONprivilege or is a superuser. max_wal_sendersleaves a session available for this receiver.- 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 an idempotent setup command, but it does not prove that 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
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 putting a password in 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. It writes 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 are more useful 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 provide timely feedback. Do not accidentally put it in a configuration where synchronous_commit=remote_apply waits for this receiver: 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 after the receiver reaches the specified LSN. The LSN must be supplied by 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
If a connection error causes an unwanted retry loop, rerun the diagnostic command with --no-loop. A password prompt is expected when the server requests password authentication; use --no-password in a non-interactive check if prompting would hang a job. A missing directory, unwritable destination, invalid slot or rejected replication connection is a configuration error, not a reason to add sudo blindly.
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
- The installed PostgreSQL client version and output directory are confirmed.
- A replication user,
pg_hba.confrule, WAL sender capacity and physical slot are verified by the administrator. - The receiver writes to a dedicated directory and reconnect behaviour is understood.
- Flush behaviour matches the backup and synchronous-standby design;
--no-syncis not used for production archiving. - Disk usage is monitored on the archive host and the primary server.
- A bounded test or clean shutdown returned status 0, and the received files are included in a tested restore process.