Cookbook recipe

Standby Promotion Failure

Applies to PostgreSQL 13–17 Last reviewed May 2026 Grounded in source
Estimated investigation4 min

Scenario

Scenario The primary database goes down. The DBA tries to promote the standby by running pg_ctl promote -D $PGDATA. The command returns success, but the standby log shows it's still in recovery mode and pg_is_in_recovery() still…

Investigation Path

Scenario

The primary database goes down. The DBA tries to promote the standby by running pg_ctl promote -D $PGDATA. The command returns success, but the standby log shows it’s still in recovery mode and pg_is_in_recovery() still returns true. The recovery_target_action was set to 'pause' in the configuration — the standby has paused at the recovery target and is waiting for pg_wal_replay_resume() before it will actually promote.

How to Identify

Conditions:

  • pg_is_in_recovery() returns true after promotion attempt
  • pg_get_wal_replay_pause_state() returns 'paused'
  • recovery_target_action = 'pause' configured
  • standby.signal file still present
  • No postgresql.conf recovery_target_time is set but recovery_target_action = 'pause' remains from a previous PITR exercise

Analysis Steps

-- Check if the standby is still in recovery
SELECT pg_is_in_recovery();
-- true = still in recovery (not promoted yet)

-- Check WAL replay pause state
SELECT pg_get_wal_replay_pause_state();
-- 'paused'     = recovery paused, waiting for pg_wal_replay_resume()
-- 'not paused' = recovery running normally

-- Check current replay position
SELECT
    pg_last_wal_receive_lsn()                   AS received_lsn,
    pg_last_wal_replay_lsn()                    AS replayed_lsn,
    pg_last_xact_replay_timestamp()             AS last_applied_txn,
    pg_is_wal_replay_paused()                   AS replay_paused;

-- Check recovery target settings
SELECT name, setting FROM pg_settings
WHERE name LIKE 'recovery%'
ORDER BY name;

-- Check if standby.signal file exists (presence = in recovery mode):
-- ls -la $PGDATA/standby.signal

Pitfalls

  • pg_ctl promote sends a signal to the PostgreSQL process but if recovery_target_action = 'pause', the process is waiting for pg_wal_replay_resume() before it will actually promote.
  • Touching the promote trigger file (touch $PGDATA/promote) is equivalent to pg_ctl promote — same limitation applies.
  • If recovery_target_time is set to a past timestamp, recovery may have stopped at that time and is waiting for manual intervention.
  • After successful promotion: standby.signal is automatically removed and pg_is_in_recovery() returns false.
  • Attempting writes on a still-in-recovery standby returns ERROR: cannot execute INSERT in a read-only transaction.

Resolution Approach

If recovery is paused at a target: call pg_wal_replay_resume() to trigger promotion. If recovery_target_action = 'pause' is set globally, remove it from postgresql.conf before the next promotion. Verify promotion with pg_is_in_recovery() = false.

This is a Pro lesson

Get every Learning Pathway and cookbook recipe — grounded in PostgreSQL source code, with diagnostics, fixes, and prevention for each topic.

Continue this lesson to learn:

  • Mitigation Actions
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

Career Impact

This scenario builds production judgment and operational confidence under pressure.

Open Career Dashboard →

Keep going

Related & next steps

Was this helpful?

← All cookbook recipes