Errata for hot_standby_feedback (2026-07-31): that post says the primary goes back to its normal vacuum schedule as soon as the standby disconnects, and tells you to watch backend_xmin in pg_stat_replication. Both are true only for a standby with no slot. When the standby streams through a slot, the reported xmin is stored in the slot, survives the disconnect, and backend_xmin is NULL the whole time; the column to watch is xmin in pg_replication_slots. Details below.
primary_slot_name is a name, and that is all it is. The slot it names is created on the upstream server, lives on the upstream server, and keeps WAL on the upstream server; the standby’s entire contribution is to say the name when it connects. That makes this the one standby parameter whose usual failure is not “wrong value” but “the thing you named isn’t there,” and the standby’s response to that is to stop.
The default is the empty string, meaning the standby streams without a slot. The context is sighup, and changing it restarts the walreceiver, which matters more than it sounds (below). It arrived in 9.4 with replication slots themselves (Andres Freund and Robert Haas), as a recovery.conf line; it became a GUC when recovery.conf was folded into postgresql.conf in 12, and reloadable in 13. The docs describe the slot’s job as being “to control resource removal on the upstream node,” which is accurate and undersells it.
What the slot does for you
The walreceiver connects and sends START_REPLICATION SLOT "name"; the walsender acquires the slot and, from then on, every position the standby reports as flushed moves the slot’s restart_lsn. That number, as of the slot’s last save to disk, is what the upstream’s checkpointer consults before recycling a WAL segment. Without a slot, the upstream keeps only what its own checkpoints, wal_keep_size, and an archiver that has not caught up require, and a standby that falls further behind than that gets requested WAL segment ... has already been removed and has to be rebuilt or fed from an archive. With a slot, the upstream keeps everything the standby has not confirmed, for as long as it takes. (Whether “as long as it takes” can be allowed to fill the primary’s disk is max_slot_wal_keep_size’s problem, and that post is the other half of this one.)
The slot also carries the standby’s hot_standby_feedback xmin, which is where the errata above come from. Without a slot, the walsender holds the standby’s xmin in its own process entry, and the entry dies with the connection; that is the “not durable” behaviour the earlier post describes. With a slot, the walsender writes the xmin into the slot instead, pg_stat_replication.backend_xmin stays NULL, and the slot keeps the xmin after the standby is gone. On 18.6: standby holding a snapshot, 50,000 rows deleted on the primary, then the standby stopped outright.
1 -- standby connected, holding a REPEATABLE READ snapshot
2 slot_name | active | xmin | backend_xmin
3 -----------+--------+------+--------------
4 standby_a | t | 765 |
5 tuples: 0 removed, 99975 remain, 50000 are dead but not yet removable
6 removable cutoff: 765
7
8 -- pg_ctl stop -m fast on the standby; no walsender left
9 slot_name | active | xmin
10 -----------+--------+------
11 standby_a | f | 765
12 tuples: 0 removed, 75006 remain, 50000 are dead but not yet removable
13 removable cutoff: 765
Start the standby again and its first feedback message moves the slot’s xmin forward; the next VACUUM removed all 50,000. Never start it again and the xmin sits there until someone drops the slot. A dead standby’s slot is therefore two leaks, not one: WAL on disk and dead rows in every table. Invalidating the slot releases both, but max_slot_wal_keep_size only invalidates once the retained WAL passes the cap, and on a quiet primary that can be weeks of held-back vacuum first. idle_replication_slot_timeout (18) does it by the clock instead. (Slot synchronization for logical failover slots, from 17, refuses to run at all without primary_slot_name and hot_standby_feedback together, for exactly this reason: the physical slot’s xmin is what keeps the catalog rows the synced slots need from being vacuumed away while the standby is down.)
There is no fallback
Name a slot that does not exist on the upstream and the standby does not shrug and stream without one. It fails, and keeps failing, every wal_retrieve_retry_interval (five seconds):
1 LOG: parameter "primary_slot_name" changed to "standby_b"
2 LOG: WAL receiver process shutdown requested
3 FATAL: terminating walreceiver process due to administrator command
4 FATAL: could not start WAL streaming: ERROR: replication slot "standby_b" does not exist
5 LOG: waiting for WAL to become available at 0/4000078
6 FATAL: could not start WAL streaming: ERROR: replication slot "standby_b" does not exist
7 LOG: waiting for WAL to become available at 0/4000078
That transcript is a healthy, streaming standby to which I applied a typo with pg_reload_conf(). Since 13 the startup process restarts the walreceiver whenever primary_conninfo or primary_slot_name changes, so a bad reload detaches a standby that was fine a second earlier. The name is checked locally for syntax (lowercase letters, digits, underscores, under 64 bytes; 19 also reserves pg_conflict_detection), and an invalid one is rejected at ALTER SYSTEM or at reload without touching the walreceiver. Existence is checked only by the upstream, every five seconds, forever. A logical slot’s name fails the same way (cannot use a logical replication slot for physical replication), and so does a physical slot another standby is already using (replication slot "standby_a" is active for PID 4158), which is what you get when you clone a standby from a standby with pg_basebackup -R and no -S: the clone inherits the parent’s primary_slot_name along with the rest of its postgresql.auto.conf, and the moment you point it at the primary the two of them fight over one slot. primary_conninfo covers that trap. The fix in every case is on the upstream, not the standby: create the slot (or free it) and the next retry succeeds with no action on the standby side.
The failure is easy to miss if the standby also has a working restore_command. Recovery alternates between the archive and streaming, so a standby with a misspelled slot name keeps up from the archive at segment granularity, pg_stat_wal_receiver is empty, replay lag looks like archive_timeout, and on the primary the slot it should be using sits with active = f, retaining WAL for a standby that never arrives. If you alert on replay lag alone you will not see this. Alert on pg_stat_wal_receiver having no row, or on active in pg_replication_slots for every slot you expect to be in use.
Create it before the backup, and reserve it
The documentation’s own example creates the slot with pg_create_physical_replication_slot('node_a_slot') and then configures the standby. Created that way the slot has a NULL restart_lsn and reserves nothing until a standby first connects through it. If the base backup is taken before that first connection, and the primary generates more WAL in between than it would keep anyway, the standby comes up, connects through a slot that exists, and still gets requested WAL segment 00000001000000000000000A has already been removed. The slot was there; it just was not holding anything. I reproduced this on 18.6 with a lazily created slot, a backup, and about a gigabyte of churn. START_REPLICATION itself succeeds (the walsender does not check restart_lsn; it fails on its first attempt to read the missing segment), and the slot’s restart_lsn was usually still NULL after dozens of failures. On the one run in three where the standby’s first status reply beat the error, the slot dutifully reserved a position that had already been recycled, which is no better.
The reserving version is pg_create_physical_replication_slot('name', immediately_reserve => true), or, better, let pg_basebackup do it:
1 pg_basebackup -h primary -U replicator -D /var/lib/postgresql/18/main -R -C -S standby_a
-C creates the slot with WAL reserved from the start of the backup, streams the backup’s own WAL through it, and -R writes both primary_conninfo and primary_slot_name = 'standby_a' into postgresql.auto.conf. That is the whole procedure. Name the slot after the standby’s cluster_name, which the walreceiver already sends as application_name unless primary_conninfo sets one, so pg_stat_replication and pg_replication_slots on the primary line up by eye.
After a failover
Slots belong to a node, not to the cluster. When a standby is promoted, every other standby you repoint at it needs a slot on the new primary with the name its primary_slot_name carries, or it stops (see above; there is no fallback). Slots on a standby survive its promotion, so a cascading standby that was already attached to it keeps going; it is the standbys that used to follow the old primary that need slots created, and the sensible time to create them is before the failover, on every node that could become primary. Patroni does this for you on 11 and later, maintaining a slot for each member on every node that could become leader. (Detaching has no shortcut: pg_drop_replication_slot() on a slot a standby is streaming through is an error, so you change the standby first and drop second.)
The old primary is the other direction: it still has the slot for the standby you just promoted, inactive and retaining WAL. If you rebuild the old primary with pg_rewind or pg_basebackup, that slot disappears, since neither carries pg_replslot over. If you leave it running as a primary, or turn it into a standby by hand, the orphan stays until you drop it.
So: a slot for every standby, created by pg_basebackup -C -S or with immediately_reserve => true before the backup, never after; max_slot_wal_keep_size on the primary so a standby that dies cannot take the primary with it; and if hot_standby_feedback is on, remember that the xmin now lives in the slot and outlives the standby. A standby without a slot is one bad night from a re-clone. A standby with a slot and no max_slot_wal_keep_size is one bad night from being the reason the primary ran out of disk. Set both.