Home/Engineer/Databases/Building a PostgreSQL Automa…
DatabasesAdvancedlinuxpostgresqlpatronietcd

Building a PostgreSQL Automatic Failover HA Cluster with Patroni and etcd

Configure a 3-node PostgreSQL HA cluster with Patroni and etcd, including automatic failover when the Primary fails and HAProxy-based load balancing.

Contents
  1. What is Patroni?
  2. Cluster Topology
  3. 1. Install the etcd Cluster (all 3 nodes)
  4. 2. Install PostgreSQL (all 3 nodes)
  5. 3. Install and Configure Patroni
  6. 4. Check Cluster Status
  7. 5. Configure HAProxy (lb server)
  8. 6. Manual Switchover / Failover
  9. Summary
  10. Troubleshooting & FAQ

What is Patroni?

Patroni is a Python-based PostgreSQL HA solution that uses a DCS (Distributed Configuration Store) such as etcd, Consul, or ZooKeeper to perform leader election and automatic failover.

Components

ComponentRole
PatroniPostgreSQL process management + failover
etcdDistributed consensus store for leader election
HAProxyApplication → Primary/Replica routing

Cluster Topology

ServerIPRole
pg1192.168.1.10PostgreSQL + Patroni + etcd
pg2192.168.1.11PostgreSQL + Patroni + etcd
pg3192.168.1.12PostgreSQL + Patroni + etcd
lb192.168.1.20HAProxy

1. Install the etcd Cluster (all 3 nodes)

Bash
sudo apt install -y etcd
Bash
# /etc/default/etcd (for pg1)
ETCD_NAME="pg1"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_LISTEN_PEER_URLS="http://192.168.1.10:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.1.10:2379,http://127.0.0.1:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.1.10:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.1.10:2379"
ETCD_INITIAL_CLUSTER="pg1=http://192.168.1.10:2380,pg2=http://192.168.1.11:2380,pg3=http://192.168.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="pg-etcd-cluster"
Bash
sudo systemctl enable --now etcd
# Check status
etcdctl endpoint status --cluster -w table

2. Install PostgreSQL (all 3 nodes)

Bash
sudo apt install -y postgresql-17
sudo systemctl disable --now postgresql   # Patroni will manage it instead

3. Install and Configure Patroni

Bash
sudo apt install -y python3-pip
pip3 install patroni[etcd] psycopg2-binary

/etc/patroni/config.yml (for pg1)

YAML
scope: pg-cluster
namespace: /db/
name: pg1

restapi:
  listen: 192.168.1.10:8008
  connect_address: 192.168.1.10:8008

etcd:
  hosts: 192.168.1.10:2379,192.168.1.11:2379,192.168.1.12:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576   # 1MB
    postgresql:
      use_pg_rewind: true
      parameters:
        wal_level: replica
        max_wal_senders: 5
        max_replication_slots: 5
        hot_standby: on
  initdb:
    - encoding: UTF8
    - data-checksums

  pg_hba:
    - host replication replicator 192.168.1.0/24 md5
    - host all all 0.0.0.0/0 md5

  users:
    admin:
      password: AdminPass!23
      options: [createrole, createdb]

postgresql:
  listen: 192.168.1.10:5432
  connect_address: 192.168.1.10:5432
  data_dir: /var/lib/postgresql/17/main
  bin_dir: /usr/lib/postgresql/17/bin
  pgpass: /tmp/pgpass

  authentication:
    replication:
      username: replicator
      password: ReplPass!23
    superuser:
      username: postgres
      password: PgPass!23
    rewind:
      username: rewind_user
      password: RewindPass!23

tags:
  nofailover: false
  noloadbalance: false
  clonefrom: false

For pg2 and pg3, change only the IP addresses in name, listen, and connect_address to match each server.

systemd Service

Bash
cat > /etc/systemd/system/patroni.service << 'EOF'
[Unit]
Description=Patroni PostgreSQL HA
After=network.target etcd.service

[Service]
User=postgres
ExecStart=/usr/local/bin/patroni /etc/patroni/config.yml
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable --now patroni

4. Check Cluster Status

Bash
patronictl -c /etc/patroni/config.yml list
CODE
+ Cluster: pg-cluster ----+----+-----------+
| Member | Host           | Role    | State   |
+--------+----------------+---------+---------+
| pg1    | 192.168.1.10   | Leader  | running |
| pg2    | 192.168.1.11   | Replica | running |
| pg3    | 192.168.1.12   | Replica | running |
+--------+----------------+---------+---------+

5. Configure HAProxy (lb server)

Bash
sudo apt install -y haproxy
CODE
# /etc/haproxy/haproxy.cfg
frontend pg_write
    bind *:5432
    default_backend pg_primary

backend pg_primary
    option httpchk GET /master
    http-check expect status 200
    server pg1 192.168.1.10:5432 check port 8008
    server pg2 192.168.1.11:5432 check port 8008
    server pg3 192.168.1.12:5432 check port 8008

frontend pg_read
    bind *:5433
    default_backend pg_replicas

backend pg_replicas
    balance roundrobin
    option httpchk GET /replica
    http-check expect status 200
    server pg1 192.168.1.10:5432 check port 8008
    server pg2 192.168.1.11:5432 check port 8008
    server pg3 192.168.1.12:5432 check port 8008

Use Patroni's REST API /master and /replica endpoints for health checks.


6. Manual Switchover / Failover

Bash
# Planned switchover (no data loss)
patronictl -c /etc/patroni/config.yml switchover pg-cluster

# Forced failover (emergency)
patronictl -c /etc/patroni/config.yml failover pg-cluster

Summary

FeatureHandled by
Leader electionetcd distributed consensus
Automatic failoverPatroni (within 30-second TTL)
Write routingHAProxy → /master
Read distributionHAProxy → /replica (round robin)

Troubleshooting & FAQ

SymptomCauseFix
Failover does not occuretcd quorum lost (2 of 3 nodes down)Recover etcd nodes → Patroni resumes automatically once majority is restored
Split-brain concernNetwork partitionPatroni prevents this via DCS (etcd) leader lease — keep etcd as the single source of truth
patronictl list is emptynamespace/scope mismatch in config.ymlVerify that namespace and scope are identical on all nodes
Slow leader promotionttl/loop_wait set too highRecommended: ttl: 30, loop_wait: 10, retry_timeout: 10

Q. What are the respective roles of Patroni and etcd for PostgreSQL? A. Patroni is the agent attached to each PostgreSQL node that manages state and executes failover. etcd is the source of truth (DCS) that stores “who is the leader” via distributed consensus. Leader election and automatic failover only work while an etcd majority is alive.

Q. Why do I need etcd if I already have Patroni? A. A single Patroni instance only knows about its own node. For multiple nodes to agree that there is exactly one leader, you need an external distributed store — that is etcd (or Consul / ZooKeeper). Without etcd you cannot prevent split-brain.

#postgresql#patroni#etcd#ha#failover#haproxy
Editorial note

AI tools may assist drafting. Check the evidence in each guide for whether commands were run and which environment was used. Confirm the current docs for your version before applying instructions, and email us about errors. email us

Topic-related official docs (auto-linked · not per-claim sources)PostgreSQL documentation ↗

Questions & answers

Ask a question about this guide. We'll review and reply.