How to Set Up Replication in PostgreSQL

How to Set Up Replication in PostgreSQL

The first time I set up PostgreSQL replication, I remember being surprised at how few moving parts there actually are once you understand the concept. It sounds intimidating — streaming WAL across servers, replication slots, standby signals — but at its core, replication is just PostgreSQL copying its write-ahead log to another server and replaying it there. In this guide, I’ll walk through exactly how to set up streaming replication from scratch, explain the settings that matter, and cover the troubleshooting steps I’ve had to use myself more times than I’d like to admit.

What Is Replication in PostgreSQL?

Replication is the process of maintaining a copy of your database on a separate server (or servers) that stays synchronized with the original. PostgreSQL supports several replication methods, but the most common and most robust is streaming replication, where the standby server connects to the primary and continuously receives WAL (write-ahead log) records as they’re generated.

There are a few flavors worth knowing about:

This guide focuses on physical streaming replication, since it’s the foundation most PostgreSQL high-availability setups are built on.

Prerequisites

Step 1: Configure the Primary Server

Open postgresql.conf on the primary and set:

listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1024

A few notes:

Next, edit pg_hba.conf to allow the standby to connect for replication:

host    replication     replicator      192.168.1.20/32        scram-sha-256

Create the replication role:

CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'use_a_strong_password';

Restart PostgreSQL so wal_level, max_wal_senders, and max_replication_slots take effect — these require a full restart, not just a reload.

Step 2: Create a Replication Slot (Recommended)

Replication slots ensure the primary retains WAL until the standby has confirmed it received it, which prevents the standby from falling irrecoverably behind if it disconnects temporarily.

SELECT pg_create_physical_replication_slot('standby1_slot');

You can view existing slots with:

SELECT * FROM pg_replication_slots;

Keep in mind that if a standby using a slot goes offline for a long time, WAL will keep accumulating on the primary and can eat up disk space. Monitor this if you go this route.

Step 3: Take a Base Backup on the Standby

On the standby machine, make sure PostgreSQL is stopped and the data directory is empty, then run:

pg_basebackup -h 192.168.1.10 -D /var/lib/postgresql/16/main -U replicator -P -R -X stream -S standby1_slot

Here’s what each flag does:

This step copies the entire data directory over the network, so expect it to take a while on larger databases. I like to run it with -P specifically so I can see it’s actually making progress rather than staring at a frozen terminal wondering if it’s hung.

Step 4: Verify the Standby Configuration

After pg_basebackup finishes, check that /var/lib/postgresql/16/main/standby.signal exists — this file (empty, just a marker) is what tells PostgreSQL to start in standby mode.

Check postgresql.auto.conf for a line similar to:

primary_conninfo = 'user=replicator password=use_a_strong_password host=192.168.1.10 port=5432 sslmode=prefer'
primary_slot_name = 'standby1_slot'

If you didn’t use -R, you’ll need to add these manually.

Step 5: Start the Standby

sudo systemctl start postgresql

Check the logs:

tail -f /var/log/postgresql/postgresql-16-main.log

Look for:

LOG:  started streaming WAL from primary at 0/3000000 on timeline 1
LOG:  consistent recovery state reached at 0/30000F8
LOG:  database system is ready to accept read-only connections

Step 6: Confirm Replication Status

On the primary:

SELECT client_addr, state, sync_state, replay_lag, write_lag, flush_lag
FROM pg_stat_replication;

On the standby:

SELECT pg_is_in_recovery();
SELECT pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();

If pg_is_in_recovery() returns true and the LSN values are advancing over time, replication is working correctly.

Synchronous Replication (Optional)

If you need stronger durability guarantees — for example, in a financial system where losing even a single committed transaction is unacceptable — you can configure synchronous replication. On the primary:

synchronous_standby_names = 'standby1'

And give your standby an application_name matching this in its primary_conninfo:

primary_conninfo = 'user=replicator host=192.168.1.10 application_name=standby1 ...'

With this in place, the primary will wait for standby1 to confirm receipt of WAL before acknowledging a commit to the client. This adds latency to every write, so weigh that trade-off against your durability requirements carefully.

Common Use Cases

Troubleshooting

Standby won’t connect: “FATAL: password authentication failed” — double check the password in primary_conninfo matches the replication role’s password, and that the pg_hba.conf entry on the primary uses the correct authentication method (scram-sha-256 is the modern default).

“replication slot does not exist” — you referenced a slot in primary_conninfo that was never created on the primary, or it was created and later dropped. Recreate it with pg_create_physical_replication_slot.

Base backup fails partway through with a timeout — increase statement_timeout or check for network instability between the two hosts. For very large databases, consider running the backup during low-traffic hours.

Standby is stuck and never reaches “ready to accept read-only connections” — check whether hot_standby is set to on in postgresql.conf, and confirm the standby can actually reach the primary on the replication port.

Disk filling up on the primary — almost always caused by an inactive replication slot holding onto WAL. Check pg_replication_slots for slots where active is false, and drop ones you no longer need with SELECT pg_drop_replication_slot('slot_name');.

Best Practices

Wrapping Up

Replication in PostgreSQL comes down to three things: enabling WAL generation at the right level, streaming that WAL to a standby, and keeping that standby in sync. Once you’ve done it manually a couple of times, it stops feeling mysterious and starts feeling like just another piece of your infrastructure. From here, you can layer on hot standby for read queries, synchronous replication for stronger durability, or a failover manager for full high availability — but the streaming replication setup above is the foundation all of that is built on.

Exit mobile version