archive_mode & archive_command
archive_mode enables WAL archiving on the primary. archive_command ships each completed WAL segment to durable storage for PITR and standby catch-up safety.
Recipe
Local archive directory for lab; S3 or NFS pattern for production.
ALTER SYSTEM SET archive_mode = on;
ALTER SYSTEM SET archive_command =
'test ! -f /mnt/wal_archive/%f && cp %p /mnt/wal_archive/%f';
ALTER SYSTEM SET archive_timeout = '300s';
-- restart PostgreSQL to enable archive_mode# Verify archiver running
psql -c "SELECT * FROM pg_stat_archiver;"
ls -l /mnt/wal_archive/ | tailWhen to reach for this: Any self-hosted PostgreSQL 18.4 cluster requiring PITR or pgBackRest-less WAL retention.
Working Example
Production patterns: NFS mount, rclone to S3, and pgBackRest integration:
# archive_command via script (atomic copy)
# /usr/local/bin/archive_wal.sh
#!/usr/bin/env bash
set -euo pipefail
WAL_PATH="$1"
WAL_NAME="$2"
DEST="/mnt/wal_archive/${WAL_NAME}"
test ! -f "$DEST" && cp "$WAL_PATH" "$DEST"
aws s3 cp "$DEST" "s3://prod-pg-wal/${WAL_NAME}" --only-if-different-- postgresql.conf equivalents
-- archive_mode = on
-- archive_command = '/usr/local/bin/archive_wal.sh %p %f'
-- wal_level = replica (minimum)
SELECT archived_count, failed_count, last_failed_wal, last_failed_time
FROM pg_stat_archiver;# Force segment switch to test archive path
psql -c "SELECT pg_switch_wal();"
sleep 2
psql -c "SELECT last_archived_wal, last_archived_time FROM pg_stat_archiver;"What this demonstrates:
archive_commandmust return exit 0 on success; non-zero incrementsfailed_count.test ! -fprevents overwrite errors on retry.archive_timeoutforces WAL switch on low-traffic systems so archives arrive timely.
Deep Dive
archive_command Placeholders
| Token | Meaning |
|---|---|
%p | Full path to WAL file to archive |
%f | WAL file name only (e.g. 000000010000000000000001) |
Common Shipping Targets
| Target | Pattern |
|---|---|
| Local/NFS | cp %p /archive/%f |
| S3 | Custom script with AWS CLI |
| pgBackRest | archive_command = 'pgbackrest --stanza=prod archive-push %p' |
pg_receivewal | Secondary daemon (less common now) |
archive_mode vs streaming
- Streaming replication sends WAL to standbys live.
- Archiving copies completed segments to durable store for PITR.
- Use both: replication for HA, archive for rewind and compliance.
Gotchas
- NFS full or stale mount -
failed_countrises; PITR chain breaks. Fix: Monitor disk andpg_stat_archiver. - Non-idempotent archive_command - Retry fails if overwrite not allowed. Fix:
test ! -fguard or pgBackRest. - archive_mode on standby only - Standby does not generate new WAL to archive for PITR of primary writes. Fix: Archive on primary (or pgBackRest from standby backup policy).
- Slow archive blocks WAL recycling - Disk pressure on primary. Fix: Fast local staging then async upload to S3.
- Forgot restart after enabling archive_mode - Appears configured but inactive. Fix: Restart PostgreSQL; confirm
SHOW archive_mode;.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| pgBackRest archive-push | Unified backup repo | Minimal shell-only setup |
| EBS snapshot + WAL on S3 | AWS native | Need granular archive_command control |
| Cloud managed backup | RDS automated backup | DIY Patroni on VMs |
FAQs
wal_level for archive?
replica or logical. minimal is insufficient for production HA + archive combo.
archive_timeout value?
Default 300s. Lower for tighter RPO on idle DBs; higher reduces archive file count on busy systems.
Patroni and archive_command?
Set in Patroni postgresql.parameters or bootstrap.dcs.postgresql.parameters; rolling restart via Patroni.
WAL compression in archive?
pgBackRest compresses. Raw cp does not; enable filesystem compression or gzip in script if needed.
How to rehydrate missing segment?
Copy from off-site replica of archive bucket; recovery cannot skip segments in chain.
Related
- PITR Basics - recovery flow
- Recovery Target Options - stop replay
- pgBackRest - archive-push
- Backup Best Practices - verify archives
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.