If you run PostgreSQL high-availability clusters with Patroni, sooner or later you’ll run into a message like this in your logs:
System ID mismatch, node primary_conninfo doesn't match
Aug 05 06:19:18 pg3 patroni[13499]: 2026-08-05 06:19:18,554 CRITICAL: system ID mismatch, node node3 belongs to a different cluster: 7664939120199926400 != 7670428155651568779
or something along the lines of a replica refusing to start because its data directory doesn’t belong to the cluster it thinks it’s part of. This is one of Patroni’s built-in safety mechanisms, and while it can look alarming, it’s actually protecting you from something much worse: silent data corruption.
In this post, we’ll break down what a System ID mismatch actually is, why Patroni cares so much about it, and walk through a complete, tested recovery procedure — including how to safely rebuild a broken replica using patronictl reinit.
Here is a reference video :
What is a System Identifier?
Every PostgreSQL cluster — meaning every result of running initdb — gets a unique 64-bit Database System Identifier. This ID is stamped into the cluster’s control file (pg_controldata) at creation time and never changes for the life of that cluster. You can check it at any time with:
sudo -u postgres pg_controldata /var/lib/postgresql/17/main | grep "Database system identifier"
This identifier is what allows PostgreSQL (and tools built on top of it, like Patroni) to definitively answer the question: “Are these two data directories actually part of the same cluster lineage, or are they two unrelated databases that happen to look similar?”
Streaming replication, WAL shipping, and pg_basebackup all depend on this identifier matching between a primary and its replicas. If it doesn’t match, the two nodes are not related — no amount of WAL replay will ever bring them into agreement, because they didn’t start from the same point in time.
Why Does a System ID Mismatch Happen?
In a healthy Patroni cluster, all nodes share the same System ID because every replica was originally built via pg_basebackup (or a restore from the same base backup) from the leader. The mismatch typically creeps in when:
- Someone manually runs
initdbon a node instead of letting Patroni bootstrap it - A node is rebuilt from an unrelated backup or snapshot
- A failed or interrupted bootstrap leaves behind a partially initialized data directory
- A node is repurposed from a different Patroni cluster without wiping its old data directory
- Disk/volume snapshots are attached to the wrong instance
Once this happens, Patroni compares:
- The cluster’s System ID, as recorded in the DCS (etcd/Consul/ZooKeeper)
- The local node’s System ID, read from its own
pg_control
If they don’t match, Patroni will refuse to start PostgreSQL as a replica of that cluster. This is by design starting replication against an unrelated cluster could corrupt data or produce a replica that looks like it’s “streaming” while actually diverging silently.
The Fix: Rebuild the Replica from the Leader
The fix is conceptually simple: the broken data directory can’t be repaired, only replaced. Patroni’s reinit command automates exactly that it wipes the replica’s data directory and rebuilds it from scratch via pg_basebackup against the current leader.
Below is the full, step-by-step recovery procedure.
Step 1: Stop Patroni on the affected node
sudo systemctl stop patroni
This prevents Patroni from repeatedly trying (and failing) to start PostgreSQL while you work.
Step 2: Kill any leftover pg_basebackup process
A previous, failed bootstrap attempt may have left a pg_basebackup process running in the background, which will interfere with a fresh attempt.
ps -ef | grep pg_basebackup
sudo pkill -f pg_basebackup
ps -ef | grep pg_basebackup # confirm it's gone
Step 3: Back Up the bad Data Directory (Don’t Delete It Yet)
Rather than deleting the mismatched directory outright, move it aside. This gives you a safety net in case something unexpected turns up during investigation.
sudo mkdir -p /var/lib/postgresql/backup
sudo mv /var/lib/postgresql/17/main \
/var/lib/postgresql/backup/main_systemid_backup
Keep in mind: this backup still contains the wrong System ID — it is not usable as a replica of the current cluster. It’s only there for forensic purposes (e.g., figuring out how the mismatch happened) or manual data recovery if needed.
Step 4: Reinitialize the Replica
From the leader or any healthy node in the cluster, run:
sudo patronictl -c /etc/patroni/patroni.yml reinit pg-cluster node3
Patroni will ask for confirmation:
Are you sure you want to reinitialize member node3? [y/N]: y
Behind the scenes, reinit tells the target node’s Patroni instance to wipe its data directory (if anything remains) and re-bootstrap using pg_basebackup streamed directly from the current leader.
Step 5: Start Patroni (If It Isn’t Already Running)
sudo systemctl start patroni
Step 6: Watch the Logs
sudo journalctl -u patroni -f
A successful rebuild looks like this:
vagrant@pg3:~$ sudo journalctl -u patroni -f
Aug 05 06:52:13 pg3 patroni[27369]: 2026-08-05 06:52:13,382 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:52:18 pg3 patroni[27369]: 2026-08-05 06:52:18,127 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:52:28 pg3 patroni[27369]: 2026-08-05 06:52:28,641 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:52:38 pg3 patroni[27369]: 2026-08-05 06:52:38,631 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:52:48 pg3 patroni[27369]: 2026-08-05 06:52:48,640 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:52:58 pg3 patroni[27369]: 2026-08-05 06:52:58,623 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:53:08 pg3 patroni[27369]: 2026-08-05 06:53:08,626 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:53:18 pg3 patroni[27369]: 2026-08-05 06:53:18,627 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:53:28 pg3 patroni[27369]: 2026-08-05 06:53:28,620 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:53:38 pg3 patroni[27369]: 2026-08-05 06:53:38,616 INFO: no action. I am (node3), a secondary, and following a leader (node2)
Aug 05 06:53:48 pg3 patroni[27369]: 2026-08-05 06:53:48,613 INFO: no action. I am (node3), a secondary, and following a leader (node2)
This tells you pg_basebackup successfully copied the leader’s cluster (including its System ID) onto node3, and streaming replication has caught up.
Step 7: Confirm Cluster Health
sudo patronictl -c /etc/patroni/patroni.yml list
Expected output:
+ Cluster: pg-cluster (7664939120199926400) ---------------+----+-------------+-----+------------+-----+
| Member | Host | Role | State | TL | Receive LSN | Lag | Replay LSN | Lag |
+--------+---------------+--------------+------------------+----+-------------+-----+------------+-----+
| node1 | 192.168.56.11 | Sync Standby | streaming | 17 | 1/A4000130 | 0 | 1/A4000130 | 0 |
| node2 | 192.168.56.12 | Leader | running | 17 | | | | |
| node3 | 192.168.56.13 | Replica | creating replica | | unknown | | unknown | |
+--------+---------------+--------------+------------------+----+-------------+-----+------------+----
Step 8: Verify the System IDs Now Match
On the leader:
vagrant@pg2:~$ sudo -u postgres /usr/lib/postgresql/17/bin/pg_controldata /var/lib/postgresql/17/main | grep "Database system identifier"
Database system identifier: 7664939120199926400
On node3:
vagrant@pg3:~$ sudo -u postgres /usr/lib/postgresql/17/bin/pg_controldata /var/lib/postgresql/17/main | grep "Database system identifier"
Database system identifier: 7664939120199926400
Both should return the identical value, e.g.:
Database system identifier: 7664939120199926400
At this point, node3 is a legitimate, verified member of the cluster’s replication lineage — not just a node that happens to be streaming.
Key Takeaways
- The System ID is immutable and cluster-defining. It’s set once at
initdbtime and never changes — it’s PostgreSQL’s way of tracking cluster lineage. - A mismatch can never be “fixed” in place. There’s no command to make one cluster’s data directory retroactively belong to another cluster’s lineage. The only correct fix is to discard the broken directory and re-copy from a legitimate source.
patronictl reinitis the safe, supported way to do this. It automates stopping the node, wiping the directory, and re-runningpg_basebackupfrom the leader — so you don’t have to do it by hand.- Always back up before wiping. Moving the old directory aside instead of deleting it outright costs almost nothing and gives you a rollback path if something else turns out to be wrong.
- This safety check is a feature, not a bug. Without it, Patroni could silently start “replicating” against an unrelated cluster, producing a replica that looks healthy in
patronictl listbut is actually holding completely different (or corrupted) data.
If you’re operating Patroni clusters at scale, it’s worth building this exact recovery sequence into your runbooks — because when a System ID mismatch shows up, hesitating or trying to “manually reconcile” the data directory is far riskier than just letting Patroni rebuild the replica from scratch.
