Postgres Failover and archive_mode: Why Backups Break
Most failover drills end the same way: the standby takes over, applications reconnect, and everyone goes home. The part that quietly broke is backup continuity. A community question summarized this week by Stefan Fercot on his pgBackRest blog describes exactly this: a standby was promoted with archive_mode=off, and the team wanted to avoid restarting the new primary. Their pgBackRest backup failed with ERROR: [087]: archive_mode must be enabled, and removing the check did not give them a safe answer. Here is what actually breaks in that situation, and how to make sure your promotion candidates are configured before you ever need them.
Why a promoted primary with archiving off is a data-loss trap
WAL archiving is what lets you roll a base backup forward to an arbitrary point in time. On a primary, archive_mode=on plus a working archive_command is what pushes each completed 16MB segment into your backup repository. When a replica is promoted with archive_mode=off, that pipeline simply does not start. The new primary accepts writes, generates WAL, and recycles segments — none of which reach the repository.
Two things go wrong at once:
1. New backups are impossible. pgBackRest will not take a backup it cannot make recoverable. It checks that archive_mode is enabled on the primary and that archive_command actually invokes pgBackRest, then refuses if either is missing. That is the [087] error above.
2. The existing backup chain has a hole. Promotion switches the server to a new WAL timeline. Everything the old primary generated stops, and everything the new primary generates begins on a timeline your repository has never seen. A backup taken after the promotion can restore fine, but it does not repair the missing WAL around the promotion point. You can still recover the old backups, but only as far as the last segment you archived on the old timeline — you cannot roll forward across the switch without the new primary’s WAL.
That distinction is the whole problem. “The restore worked” and “my timeline is recoverable” are different claims.
archive-mode-check=n is not the escape hatch
The tempting workaround is to disable pgBackRest’s check. It does not bypass the archive_mode requirement, and it is not meant to. The option exists so you can legitimately use archive_mode=always on a standby that pushes WAL into the same repository as the primary — and disabling it means you accept responsibility that only one archiver ever writes to that repository, because WAL pushed from a standby can be logically identical to primary WAL but carry different checksums. Bypassing the guard transfers a safety assumption onto whoever maintains the patched build. One successful proof of concept does not validate every failure and recovery scenario.
The configuration that makes promotion safe
Set this on the primary and on every node that could be promoted:
# postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=demo archive-push %p'
archive_timeout = 300 # optional: bounds RPO on low-write systems
Two operational details matter more than the settings themselves:
archive_mode requires a restart; archive_command only requires a reload. That asymmetry is why this bites people. You can fix a broken archive command at 3am with pg_ctl reload. You cannot fix a missing archive_mode without bouncing the server — and bouncing the brand-new primary is exactly what the team in the story wanted to avoid.
On a standby, archive_mode=on archives nothing. A standby only pushes its own WAL when it is set to always; on starts working the moment the node becomes primary. If you take backups from a standby with pgBackRest’s backup-standby=y (plus pg2-host / pg2-path entries in /etc/pgbackrest.conf), plan the archiving mode for that topology deliberately instead of inheriting whatever the old primary had.
Patroni, repmgr, and the “it just promotes” assumption
Automation does not save you here, because archiving is a PostgreSQL-level setting your tooling passes through. Patroni’s own sample configuration ships archive_mode commented out:
# postgres0.yml / DCS postgresql.parameters
# archive_mode: "on"
# archive_timeout: 1800s
# archive_command: 'pgbackrest --stanza=demo archive-push %p'
Uncomment it for the whole cluster, not one node. Patroni will flag pending_restart when the value changes, and on a replica that restart is cheap — which is the point: do it long before the day you need it.
Patroni does have one relevant backstop. On promote, it tries to archive any WAL segments that still have .ready files in pg_wal/archive_status, specifically to prevent the loss of those segments (and a failed restart of the old leader) when archive_mode was not always before the promotion. It also reads archive_mode and archive_command on the rewind path. Treat both as a safety net that reduces damage, not as a substitute for correct configuration — they can only act on segments still on disk.
With repmgr, the equivalent trap is a drifted postgresql.conf: if archiving is configured per node instead of in a shared include, one node will promote without it. Keep it in the common config file and diff it across the cluster.
Pre-promotion checklist
archive_mode = on(oralwayswhere the topology requires it) on every promotion candidate, verified by reading the running value, not the file.archive_commandset and known-good — apgbackrest --stanza=<name> checkpasses from that node.- Repository reachable and writable from the candidate, with a retention policy (
repo1-retention-full) that matches your recovery window. restore_commandconfigured on nodes that may need to fetch WAL, so an old leader can rejoin.- Alerting on archiving, because a silently failing archive command is the same outage as
archive_mode=off, just slower.
Monitor archiving, and test restores — not just backups
SELECT archived_count, failed_count, last_archived_wal,
last_failed_wal, last_failed_time
FROM pg_stat_archiver;
-- backlog of segments waiting to be archived
SELECT count(*) FROM pg_ls_archive_statusdir() WHERE name LIKE '%.ready';
Then test recovery properly: restore a base backup into a scratch instance, move it across a past promotion with recovery_target_timeline='latest', and confirm the timeline switch replays cleanly. A backup library that has never been restored across a timeline switch is an untested assumption.
While you are in the maintenance window: patch PgBouncer
PgBouncer 1.26.0 (released September 23, 2026) fixes three CVEs: two denial-of-service issues reachable by unauthenticated clients (a crash on a SCRAM client-final-message without a nonce, and an infinite loop from an integer overflow in packet buffer growth), and one where a malicious PostgreSQL server triggers unbounded work at login via an unchecked SCRAM iteration count. If your pooler sits in front of your primary, that is the same maintenance window. Note that the deprecated online restart (-R) is gone in this release, so plan pooler restarts as rolling connection drains rather than expecting a hot restart.
Sources: pgBackRest and PostgreSQL failover: why archive_mode matters (Stefan Fercot, September 23, 2026) · pgBackRest archive-mode-check option · PostgreSQL continuous archiving and PITR · PgBouncer 1.26.0 release notes
