Build an idempotent disposable WAL archive, observe retries and lag, and reason about retention from recovery requirements rather than deleting pg_wal files when disk usage rises.

Continuous Archiving, archive_command, WAL Retention, and Restore Commands

Build an idempotent disposable WAL archive, observe retries and lag, and reason about retention from recovery requirements rather than deleting pg_wal files when disk usage rises.

Intermediate → Advanced180–240 minutesRestore-first backup engineering labCurrent patched PostgreSQL 18.xCore PostgreSQL client/server utilities onlyOwner/backup/replication privileges where explicitly statedPhysical/PITR labs use separate disposable local clusters on ports 55434–55435No managed-service or paid dependencyLast reviewed: August 2026

Learning outcomes

A base backup freezes a physical starting point. Point-in-time recovery becomes possible only when you can also supply every required WAL segment after that starting point. Continuous archiving copies completed WAL segments to durable archive storage before PostgreSQL recycles them. The archive path becomes part of your recovery chain and must be monitored like production storage.

01

Configure disposable archive_mode/archive_command safely.

02

Explain the success/failure contract and why archive commands must be idempotent.

03

Observe archive success/failure with pg_stat_archiver and WAL switching.

04

Build a restore_command that returns nonzero when a requested file is absent.

05

Explain why archive backlog can grow pg_wal and why manual deletion is unsafe.

Recovery-chain rule

A base backup plus a gap in archived WAL is not a valid PITR chain beyond that gap. Monitor continuity and restore it in drills; do not infer recoverability from the existence of many archive files.

1. Use a separate disposable archival cluster

archive_mode is a server-start setting. Rather than changing the long-lived ServiceHub lab, use a separate local cluster, for example port 55434, with a dedicated archive directory. The exact initdb/pg_ctl paths differ across Windows, Linux, packages, and containers, so run the commands from the PostgreSQL 18 binary directory available on your system.

shell · Unix-style disposable directories
mkdir -p ./ch13_archive_cluster ./ch13_wal_archiveinitdb -D ./ch13_archive_clusterchmod 700 ./ch13_wal_archive
text · archive settings pattern
wal_level = replicaarchive_mode = onarchive_command = 'test ! -f /ABSOLUTE/ch13_wal_archive/%f && cp %p /ABSOLUTE/ch13_wal_archive/%f'logging_collector = on

The example refuses to overwrite an existing archive file. In a production archive, an existing filename should normally be treated as success only after you have proven the existing bytes are identical; blindly overwriting can destroy the original timeline history.

2. Success means zero exit status

PostgreSQL treats archive-command exit status zero as “this completed segment has been archived and may eventually be recycled.” A nonzero exit means failure and the archiver retries later. This makes idempotency essential: the same segment can be requested more than once after retries or restarts.

sql · observe archive statistics
SELECT archived_count, last_archived_wal, last_archived_time,       failed_count, last_failed_wal, last_failed_time, stats_resetFROM pg_stat_archiver;
sql · force a WAL segment switch for a controlled test
SELECT pg_switch_wal();

If the workload is quiet, a segment may otherwise take a long time to fill. archive_timeout can force periodic switching, but setting it very low wastes archive capacity because even early-switched archived segments occupy full segment files.

3. Deliberately break and repair the archive

In the disposable lab, temporarily make the archive directory unwritable or point archive_command to a missing location, then generate/switch WAL. Observe failed_count and server logs. Repair the target and verify that retries eventually advance last_archived_wal.

sql · diagnose backlog evidence
SELECT * FROM pg_stat_archiver;SELECT pg_size_pretty(sum(size)) AS pg_wal_bytesFROM pg_ls_waldir();
Wrong approach

Deleting files directly from PGDATA/pg_wal because the directory is large can remove WAL still required by crash recovery, archiving, replication slots, or standbys and can make the cluster unrecoverable. Diagnose the retaining requirement first.

4. restore_command is the reverse contract

During archive recovery PostgreSQL asks restore_command for a specific filename. The command copies it to the path PostgreSQL supplies. A missing requested file must return nonzero. Requests can include timeline-history files ending in .history; not every missing request at the end of recovery is an operator incident.

text · restore_command pattern
restore_command = 'cp /ABSOLUTE/ch13_wal_archive/%f %p'

Archive files must be protected like database data: WAL can reveal essentially all database changes. Encryption, access control, off-host copies, retention policy, and monitoring belong to the backup design even though PostgreSQL itself only invokes the command/module.

5. Retention follows the longest supported recovery objective

To recover a base backup, you need an unbroken WAL sequence from at least the backup's start through the target. If you keep seven daily base backups but only one day of WAL, the older backups cannot satisfy a seven-day PITR objective. Conversely, keeping WAL forever without tested retention/cleanup can exhaust storage.

psql · record WAL generation rate during a lab interval
SELECT pg_current_wal_insert_lsn() AS wal_start \gset-- run representative workload for a measured intervalSELECT pg_size_pretty(         pg_wal_lsn_diff(pg_current_wal_insert_lsn(), :'wal_start'::pg_lsn)       ) AS generated_wal;

Use measured WAL generation plus retention duration and safety margin to plan archive capacity; never invent a universal GB/day value.

Check your understanding

  1. Why must archive_command be idempotent?
  2. What does a nonzero exit tell PostgreSQL?
  3. Why can pg_wal grow when archiving is broken?
  4. What continuity does PITR require?
Review the answers

Segments can be retried, so rerunning the command must not corrupt or silently replace the canonical copy. Nonzero means archiving failed and should be retried. PostgreSQL retains unarchived WAL rather than recycling it. PITR needs an unbroken WAL chain from a usable base backup through the chosen target.

6. Archive lag is both an RPO and capacity signal

pg_stat_archiver tells you the most recently archived WAL file and success/failure timestamps, but it does not directly tell you “seconds of recoverability.” Correlate those fields with current WAL generation, archive storage, and a known business marker. If the primary has committed important work whose WAL has not yet reached durable off-host archive storage, disaster RPO is worse than the transaction's local commit durability.

Archive backlog also consumes local pg_wal because PostgreSQL cannot recycle segments that still need archiving. If the filesystem fills, PostgreSQL can be forced offline. Therefore alerts should cover archive failure/staleness, local WAL growth rate, free space, and archive destination health together.

archive_command versus archive_library

Core PostgreSQL supports both a shell archive_command and an archive module configured with archive_library. A simple local lab is clearer with a shell command; production systems may use a purpose-built archive library or external backup tool. Either way, PostgreSQL's contract is the same: completed WAL must not be reported as successfully archived until a durable, retrievable copy exists.

Security and immutability

Archived WAL can reconstruct database changes, so archive permissions are sensitive data permissions. Keep the database server's write identity separate from any administrative identity capable of deleting long-term backup retention when practical. An archive that the compromised primary can rewrite or delete without constraint shares the same failure domain as production.

Authoritative references

Backup and recovery behavior is version-, topology-, privilege-, and storage-sensitive. These primary PostgreSQL sources define the mechanisms used in this lesson.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.