Skip to main content
RunBook Academy

PostgreSQLXIV · Replication, Slots and Read ReplicasReplication

Building a standby

Intermediate⏱ ~30 min🧪 Lab requiredpg_basebackuppsql

What you'll learn

  • Build a streaming standby from scratch
  • Verify it is actually replicating rather than merely running
  • Choose between a slot and an archive for catch-up safety
  • Avoid the procedural mistakes that produce a broken standby

Prerequisites

Practice

Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27

Not yet marked complete on this device.

Building a standby is five steps. Verifying one is four checks, and the checks matter more, because a standby that starts and does not replicate looks exactly like a standby that works.

On the primary

-- 1. a role that can stream WAL and nothing else
CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD '...';

-- 2. a slot, so the primary retains WAL this standby has not consumed
SELECT pg_create_physical_replication_slot('standby1');
# 3. pg_hba.conf, above any reject rules
host    replication   repl  10.0.0.0/24   scram-sha-256
# 4. postgresql.conf. wal_level=replica is the default in 18.
wal_level = replica
max_wal_senders = 10          # default 10; one per standby plus headroom
max_replication_slots = 10
wal_log_hints = on            # required for pg_rewind later (Part XV)

max_wal_senders needs headroom beyond the standby count, because pg_basebackup -Xstream consumes one for the duration of a backup, and so does each pg_receivewal.

On the standby

Service impact possiblethe base backup that becomes a standby
$ pg_basebackup -h rbpg-prim -U repl -D /var/lib/postgresql/18/docker \
  -Fp -Xstream -c fast -P -R -S standby1
23650/23650 kB (100%), 1/1 tablespace

-Fp (plain) because a standby needs a data directory, not a tarball. -R writes the recovery configuration. -S standby1 binds the backup and the subsequent streaming to the slot, so no WAL is missed between the two.

Read-only / Safewhat -R wrote
$ cat postgresql.auto.conf; ls standby.signal
primary_conninfo = 'user=repl password=replpass channel_binding=prefer
 host=''rbpg-prim'' port=5432 sslmode=prefer ...'
primary_slot_name = 'standby1'

standby.signal

standby.signal is what makes it a standby. Its relationship to recovery.signal from lesson XIII-06 is exact and worth stating: both put the cluster into recovery, and they differ in what happens at the end of the available WAL. recovery.signal means “recover to a target, then stop.” standby.signal means “recover, and keep waiting for more.” A directory containing neither starts as a normal primary and ignores every recovery setting.

Then add whatever the standby needs locally:

listen_addresses = '*'
hot_standby = on                      # default on; allows read queries
hot_standby_feedback = off            # lesson XIV-07
max_standby_streaming_delay = 30s     # lesson XIV-07
restore_command = 'cp /archive/%f %p' # the fallback path

The four checks

Starting is not evidence. These are.

Read-only / Safe1. the standby believes it is a standby
$ SELECT pg_is_in_recovery();
 pg_is_in_recovery
-------------------
t
Read-only / Safe2. the primary can see it, and it is streaming
$ SELECT application_name, state, sync_state FROM pg_stat_replication;
 application_name |   state   | sync_state
------------------+-----------+------------
walreceiver      | streaming | async
Read-only / Safe3. data actually arrives
$ -- primary:  INSERT INTO t DEFAULT VALUES;
-- standby:
SELECT count(*) AS rows_on_standby FROM t;
 rows_on_standby
-----------------
             1
Read-only / Safe4. it refuses writes
$ INSERT INTO t DEFAULT VALUES;
ERROR:  cannot execute INSERT in a read-only transaction

Check 2 is the one people skip and the one that catches the most. pg_is_in_recovery() returning true only means the standby is in recovery — it says nothing about whether anything is arriving. A standby replaying from an archive with a dead streaming connection passes check 1 and fails check 2.

Slot, archive, or both

The standby needs WAL the primary has not yet recycled. Three arrangements, with different failure modes:

ArrangementIf the standby is down for a long timeRisk
Slot onlyPrimary retains WAL indefinitelypg_wal fills the disk
Archive onlyStandby catches up from the archiveArchive must be working
Slot + max_slot_wal_keep_sizeSlot is invalidated at the limitStandby needs a rebuild
Slot + archiveSlot capped, archive covers the gapMost moving parts

The last row is the robust one and lesson XIV-06 is about why the first row is dangerous.

What to take from this

  • Five steps: role, slot, pg_hba.conf, primary settings, pg_basebackup -R.
  • Set wal_log_hints = on before you need pg_rewind, not after.
  • standby.signal means “keep waiting for more WAL”; recovery.signal means “stop at a target”.
  • Four checks: in recovery, visible on the primary as streaming, data arrives, writes refused. Check 2 is the one people skip.
  • Stop and confirm before replacing a data directory.
  • Slot plus archive is the robust arrangement.
  • max_connections and friends must be at least as large on the standby — raise them there first.

Cross-course references

  • Ansible for Production Sysadmins — Part XII (Idempotency) covers building a standby the same way twice, which is what makes a rebuild during an incident survivable.
  • Secrets, PKI & Certificate Management — Part XIII (Dynamic credentials) covers the replication credential, and Part VII (TLS for operators) covers encrypting the stream it authenticates.

Quiz

Knowledge check · 6 questions

  1. Q1. A standby returns true from pg_is_in_recovery() and serves queries with data that is hours old. What check would have caught this?

  2. Q2. Why should wal_log_hints be enabled when a standby is first built rather than when pg_rewind is needed?

  3. Q3. max_connections is raised from 200 to 500 on a primary. The standby is left at 200. When does this become a problem?

  4. Q4. Which settings must be at least as large on a standby as on its primary? Select all that apply.

  5. Q5. standby.signal and recovery.signal both put a cluster into recovery, and differ in what happens when the available WAL runs out.

  6. Q6. Compare a replication slot and a WAL archive as the mechanism that lets a standby catch up, and say why using both is usually right.

Passing score: 75%. Answers are checked in this browser.