max_active_replication_origins is a ten-year-old XXX comment that finally got paid off. It arrived in PostgreSQL 18, and it exists because the thing it controls had been borrowing another parameter’s number since 9.5. On 14 through 17, the number of replication origins a subscriber can track is set by max_replication_slots, a parameter whose real job is on the publisher; from 16 the documentation lists it in the subscriber section anyway, with a note that it “also applies on a sending server, but with a different meaning.” A separate parameter was first proposed in 2021 and went nowhere. It waited for 18.
The default is 10, the range is 0 to 262143, and the context is postmaster: you cannot change it without a restart. The ceiling was copied from max_replication_slots, where the source marks it /* XXX? */, and it’s decorative; an origin ID is 16 bits, so the catalog holds at most 65,534 origins no matter what you set here.
A replication origin is a name for a source of changes, with a two-byte ID, kept in pg_replication_origin. Every subscription gets one (pg_<subscription oid>), and every table synchronization worker gets its own for as long as it runs (pg_<subscription oid>_<relation oid>; that’s the PostgreSQL 14 change that let the initial copy commit in more than one transaction). When the apply worker commits a remote transaction locally, the local commit record carries the origin ID and the remote commit LSN. That is what makes the subscriber crash-safe on asynchronous commit: after a crash, replay rebuilds “how far did I get from each source” out of the commit records, and nobody had to fsync per remote transaction to earn that. The origin also rides along to the publisher’s output plugin, which is how origin = none on a subscription (16 and later) stops a bidirectional setup from replaying its own changes back to itself. The decoding side of all this is the logical_decoding_work_mem tour; origins are the subscriber’s bookkeeping.
The “how far did I get” table is a shared memory array, one 56-byte entry per origin, checkpointed to pg_logical/replorigin_checkpoint and read back at startup. max_active_replication_origins is the length of that array. That’s the whole parameter. It does not limit the catalog; pg_replication_origin_status shows the array and pg_replication_origin shows the catalog, and an entry appears in the former only once something starts using it.
Which brings us to how it fails, which is quieter than you’d want. CREATE SUBSCRIPTION doesn’t touch the array; it writes the catalog row and returns success. The apply worker then starts, tries to claim an entry, and dies: could not find free replication state slot for replication origin with ID 4, with a hint to raise the parameter, and the launcher respawns it every wal_retrieve_retry_interval (five seconds by default), forever. Nothing in the DDL told you.
The subtler version is the one the documentation means when it tells you to leave some reserve for table synchronization. I set the limit to two and created two subscriptions. Both apply workers got entries. The second subscription’s initial copy never finished: its synchronization worker needed an origin of its own, there wasn’t one, and it looped on the same error every five seconds with the table stuck in state d in pg_subscription_rel. Exactly as many entries as subscriptions is a configuration that works right up until someone adds a table. The reserve is one per concurrently running synchronization worker, bounded by max_sync_workers_per_subscription (default 2) per subscription and by max_logical_replication_workers overall.
Set it below the number of entries already in the checkpoint file and the server won’t start at all: PANIC: could not find free replication state, increase "max_active_replication_origins", startup process aborts, postmaster gives up. Set it to zero and the logical replication launcher exits on startup with a message saying so, and no subscription on that server runs.
The standby version
Physical standbys of a subscriber replay those origin records too, into an array sized by their max_active_replication_origins. Five parameters (max_connections, max_prepared_transactions, max_locks_per_transaction, max_wal_senders, max_worker_processes) are recorded in the control file, and recovery on a standby won’t proceed with a value smaller than the primary’s. This is not one of them. A standby with a smaller value than the primary needs starts cleanly, reaches consistency, serves queries, and then dies the moment it replays an origin it has no room for: FATAL: could not find free replication state slot for replication origin with ID 5, CONTEXT: WAL redo ... for ReplicationOrigin/SET, startup process exits, server shuts down. If the base backup already has more origins checkpointed than the standby allows, it never gets that far; it PANICs reading the checkpoint file. This was reported against 18 on pgsql-bugs in September 2025, turns out to predate 18 (17 does the same thing with max_replication_slots), and as of this writing nothing enforces it. So treat it like the five that are enforced: raise it on the standbys first, lower it on the primary first.
pg_upgrade from 17 to 18 checks that the new cluster’s value is at least the old cluster’s subscription count and refuses to proceed otherwise, which is the right moment to notice that whatever you had raised max_replication_slots to on the old subscriber does not carry over. That number belongs in this parameter now, and if the node doesn’t publish anything, max_replication_slots can go back to being a publisher-side question.
Fifty-six bytes an entry. There is no reason to be stingy. Set it to the subscriptions you expect plus max_logical_replication_workers, then double it; on a node whose only job is subscribing, 100 costs under six kilobytes of shared memory and means nobody restarts the server the day a subscription gets added. Set it on the standbys first. On 14 through 17 all of the above, standby crash included, is true of max_replication_slots on the subscriber, and the arithmetic is the same.