{"id":5143,"date":"2026-11-07T03:05:33","date_gmt":"2026-11-06T21:35:33","guid":{"rendered":"https:\/\/cpanelfree.com\/blog\/setting-up-postgresql-high-availability-with-patroni-etcd-and-pgbouncer-on-linux-vps\/"},"modified":"2026-11-07T03:05:33","modified_gmt":"2026-11-06T21:35:33","slug":"setting-up-postgresql-high-availability-with-patroni-etcd-and-pgbouncer-on-linux-vps","status":"publish","type":"post","link":"https:\/\/cpanelfree.com\/blog\/setting-up-postgresql-high-availability-with-patroni-etcd-and-pgbouncer-on-linux-vps\/","title":{"rendered":"Setting Up PostgreSQL High Availability with Patroni, etcd, and PgBouncer on Linux VPS"},"content":{"rendered":"<p>Operating a mission-critical relational database on Linux VPS infrastructure demands an architecture that survives sudden hypervisor panics, kernel out-of-memory kills, and network partitions without data corruption or manual failover delays. Traditional PostgreSQL streaming replication provides data redundancy, but when the primary node drops offline at 03:00 AM, human intervention to promote a standby and redirect application traffic inevitably inflates Recovery Time Objective (RTO) into dozens of minutes. By coupling Patroni as an intelligent supervisor, etcd as a fault-tolerant Distributed Configuration Store (DCS), and PgBouncer for transparent connection pooling and endpoint routing, systems engineers can deploy a production-ready, self-healing database cluster on <a href=\"https:\/\/cpanelfree.com\">CpanelFree<\/a> staging nodes and enterprise cloud environments.<\/p>\n<p><!-- more --><\/p>\n<h2>What is PostgreSQL High Availability with Patroni, etcd, and PgBouncer?<\/h2>\n<div class=\"wp-block-group\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:20px 0\">\n<p style=\"margin:0;font-size:15px;line-height:1.6;color:#333\"><strong>Direct Answer:<\/strong> PostgreSQL high availability with Patroni, etcd, and PgBouncer delivers automated failover, distributed consensus, and connection management across Linux VPS nodes. etcd provides a resilient distributed configuration store (DCS), Patroni continuously monitors node health and orchestrates Raft-driven leader election, while PgBouncer pools connections and routes client traffic seamlessly to prevent application connection storms during automated failover transitions.<\/p>\n<\/div>\n<p>In modern Linux infrastructure environments, achieving authentic High Availability (HA) requires solving three independent distributed systems challenges simultaneously: state consensus, node orchestration, and client connection virtualization. If any of these layers is misconfigured, the cluster risks either catastrophic split-brain conditions (where two nodes accept simultaneous writes) or cascading connection exhaustion when thousands of client sockets overwhelm the newly promoted primary.<\/p>\n<p>The standard reference architecture consists of three virtual private servers (nodes <code>pg-node01<\/code>, <code>pg-node02<\/code>, and <code>pg-node03<\/code>), each hosting an instance of <code>etcd<\/code>, a <code>patroni<\/code> daemon managing a local PostgreSQL 16+ engine, and a co-located or fronted <code>pgbouncer<\/code> service.<\/p>\n<h2>High-Availability Architectural Matrix: Manual vs. Patroni Cluster<\/h2>\n<p>Before evaluating the step-by-step implementation, review the fundamental operational divergence between legacy primary-standby configurations and a modern Patroni-orchestrated consensus cluster:<\/p>\n<figure class=\"wp-block-table is-style-regular\">\n<table style=\"width:100%;border-collapse:collapse;margin:24px 0;font-size:15px;text-align:left\">\n<thead style=\"background:#001b41;color:#ffffff\">\n<tr>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Architecture Metric \/ Capability<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Standby Streaming (Manual)<\/th>\n<th style=\"padding:12px 16px;border-bottom:2px solid #001b41\">Patroni + etcd + PgBouncer (HA Cluster)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Failover RTO (Recovery Time Objective)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">10\u201345 minutes (Manual sysadmin intervention)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">&lt; 10 seconds (Automated consensus election)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Split-Brain Protection<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Vulnerable without manual fencing\/STONITH<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Guaranteed via etcd Raft quorum lease TTL<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Connection Handling Under Load<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Direct backend forks (~10MB RAM per client)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">PgBouncer transaction pooling (&gt;10,000 clients)<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Client Routing \/ Service Discovery<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Manual DNS update or IP takeover lag<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Health endpoint dynamic routing via Patroni REST API<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Replica Resynchronization<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Complex manual pg_rewind \/ pg_basebackup<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Automated pg_rewind self-healing recovery<\/td>\n<\/tr>\n<tr>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">Cluster Configuration Drift<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7\">High (individual postgresql.conf per server)<\/td>\n<td style=\"padding:12px 16px;border-bottom:1px solid #e7e7e7;color:#20B038;font-weight:600\">Synchronized cluster-wide DCS configuration<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<h2>Step 1: Linux Kernel and OS Tuning for PostgreSQL VPS Instances<\/h2>\n<p>High-concurrency PostgreSQL clusters running on Linux virtual machines require dedicated kernel memory management parameters. PostgreSQL relies heavily on the operating system page cache alongside its internal <code>shared_buffers<\/code>. Furthermore, aggressive virtual memory overcommitting or excessive dirty page accumulation can trigger kernel latency spikes or catastrophic Out-Of-Memory (OOM) kills during high write throughput.<\/p>\n<p>Deploy the following configuration file across all cluster nodes at <code>\/etc\/sysctl.d\/99-postgresql-ha.conf<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/sysctl.d\/99-postgresql-ha.conf\n# Linux Kernel Optimization for PostgreSQL HA, etcd, and PgBouncer\n\n# Disable memory overcommit to prevent unpredictable OOM kills of the primary\nvm.overcommit_memory = 2\nvm.overcommit_ratio = 80\n\n# Keep page cache flushing predictable and continuous rather than bursty\nvm.dirty_background_ratio = 5\nvm.dirty_ratio = 10\n\n# Minimize swapping aggressiveness while retaining basic swap emergency cushion\nvm.swappiness = 1\n\n# Prevent memory fragmentation on high memory VPS instances\nvm.zone_reclaim_mode = 0\n\n# Network stack tuning for high-frequency consensus heartbeats and connection pools\nnet.core.somaxconn = 4096\nnet.ipv4.tcp_max_syn_backlog = 8192\nnet.ipv4.tcp_tw_reuse = 1\nnet.ipv4.tcp_fin_timeout = 15\n\n# Increase socket buffer sizes for heavy replication streams\nnet.core.rmem_default = 262144\nnet.core.wmem_default = 262144\nnet.core.rmem_max = 16777216\nnet.core.wmem_max = 16777216\n\n# File descriptor ceiling for busy database and pooling instances\nfs.file-max = 2097152<\/code><\/pre>\n<p>Apply the tuned parameters immediately across all nodes without rebooting:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo sysctl --system<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Architecture Note on OOM Defense:<\/strong> Setting <code>vm.overcommit_memory = 2<\/code> ensures the Linux kernel will strictly refuse memory allocations that exceed <code>(RAM * vm.overcommit_ratio \/ 100) + Swap<\/code>. This completely eliminates the risk of the Linux OOM-killer arbitrarily sending <code>SIGKILL<\/code> to PostgreSQL backend processes, which would trigger immediate crash recovery on the primary.<\/p>\n<\/blockquote>\n<h2>Step 2: Provisioning the 3-Node etcd Consensus Cluster<\/h2>\n<p>Patroni requires a Distributed Configuration Store (DCS) to maintain leader leases and cluster topology state. etcd utilizes the Raft consensus algorithm, meaning a minimum of three nodes is required to tolerate the failure of one node while preserving quorum (<code>(N\/2) + 1 = 2<\/code> votes). Running etcd on only two nodes provides zero fault tolerance, as the failure of either node drops the quorum below the necessary majority.<\/p>\n<p>Install etcd on all three nodes (<code>10.0.0.11<\/code>, <code>10.0.0.12<\/code>, <code>10.0.0.13<\/code>). Here is the complete production configuration for node 1 at <code>\/etc\/etcd\/etcd.yml<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/etcd\/etcd.yml (Node 1: pg-node01)\nname: 'pg-node01'\ndata-dir: '\/var\/lib\/etcd\/pg-cluster.etcd'\nlisten-peer-urls: 'http:\/\/10.0.0.11:2380'\nlisten-client-urls: 'http:\/\/10.0.0.11:2379,http:\/\/127.0.0.1:2379'\ninitial-advertise-peer-urls: 'http:\/\/10.0.0.11:2380'\nadvertise-client-urls: 'http:\/\/10.0.0.11:2379'\n\n# Raft consensus peer cluster discovery\ninitial-cluster: 'pg-node01=http:\/\/10.0.0.11:2380,pg-node02=http:\/\/10.0.0.12:2380,pg-node03=http:\/\/10.0.0.13:2380'\ninitial-cluster-token: 'etcd-pg-ha-token'\ninitial-cluster-state: 'new'\n\n# Heartbeat and election timeouts (tuned for stable Linux VPS networking)\nheartbeat-interval: 250\nelection-timeout: 1250\nauto-compaction-retention: '1'\nsnapshot-count: 10000\nmax-request-bytes: 1572864\nquota-backend-bytes: 8589934592<\/code><\/pre>\n<p>Enable and start the etcd service across all nodes:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now etcd\netcdctl --endpoints=http:\/\/127.0.0.1:2379 endpoint health\netcdctl --endpoints=http:\/\/127.0.0.1:2379 member list -w table<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Consensus Quorum Warning:<\/strong> Never deploy an even number of etcd consensus nodes. A 4-node cluster requires 3 nodes for quorum; if two nodes partition, the entire cluster halts writes. A 3-node cluster also requires 2 nodes for quorum, meaning 3 nodes and 4 nodes provide the exact same fault tolerance (1 node failure), but 4 nodes introduce an additional failure surface.<\/p>\n<\/blockquote>\n<h2>Step 3: Deploying and Configuring Patroni<\/h2>\n<p>Patroni acts as a customized supervisor that wraps PostgreSQL. It registers with etcd, acquires a leader lease lock with a Time-To-Live (TTL), and bootstraps the PostgreSQL database engine automatically. Standby nodes query etcd, identify the current primary, and configure native streaming replication via <code>pg_basebackup<\/code> without manual intervention.<\/p>\n<p>Install Patroni with python etcd3 support:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo apt-get update &amp;&amp; sudo apt-get install -y postgresql-16 postgresql-contrib patroni python3-etcd3<\/code><\/pre>\n<p>Place the following production configuration at <code>\/etc\/patroni\/patroni.yml<\/code> on <code>pg-node01<\/code> (adjusting node IP addresses accordingly for <code>pg-node02<\/code> and <code>pg-node03<\/code>):<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/patroni\/patroni.yml\nscope: postgres-ha-cluster\nnamespace: \/service\nname: pg-node01\n\n# Patroni REST API for health checks and orchestrator commands\nrestapi:\n  listen: 10.0.0.11:8008\n  connect_address: 10.0.0.11:8008\n\n# Distributed Configuration Store connection\netcd3:\n  hosts:\n    - 10.0.0.11:2379\n    - 10.0.0.12:2379\n    - 10.0.0.13:2379\n\nbootstrap:\n  dcs:\n    ttl: 30\n    loop_wait: 10\n    retry_timeout: 10\n    maximum_lag_on_failover: 1048576\n    synchronous_mode: false\n    postgresql:\n      use_pg_rewind: true\n      use_slots: true\n      parameters:\n        shared_buffers: 4GB\n        work_mem: 32MB\n        maintenance_work_mem: 512MB\n        effective_cache_size: 12GB\n        wal_level: replica\n        max_wal_size: 16GB\n        min_wal_size: 2GB\n        checkpoint_completion_target: 0.9\n        checkpoint_timeout: 15min\n        archive_mode: \"on\"\n        archive_command: \"bin\/true\"\n        wal_keep_size: 2048MB\n        hot_standby: \"on\"\n        track_io_timing: \"on\"\n        max_connections: 300\n\n  initdb:\n    - encoding: UTF8\n    - data-checksums\n\n  pg_hba:\n    - host replication replicator 10.0.0.0\/24 md5\n    - host all all 10.0.0.0\/24 md5\n    - host all all 127.0.0.1\/32 md5\n\npostgresql:\n  listen: 10.0.0.11:5432,127.0.0.1:5432\n  connect_address: 10.0.0.11:5432\n  data_dir: \/var\/lib\/postgresql\/16\/patroni\n  bin_dir: \/usr\/lib\/postgresql\/16\/bin\n  pgpass: \/var\/lib\/postgresql\/.pgpass\n  authentication:\n    replication:\n      username: replicator\n      password: SuperSecretReplicationPassword2026\n    superuser:\n      username: postgres\n      password: SuperSecretPostgresAdminPassword2026\n\ntags:\n  nofailover: false\n  noloadbalance: false\n  clonefrom: false\n  nosync: false<\/code><\/pre>\n<p>Create the systemd service unit to supervise Patroni at <code>\/etc\/systemd\/system\/patroni.service<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/systemd\/system\/patroni.service\n[Unit]\nDescription=Patroni PostgreSQL High Availability Orchestrator\nAfter=network.target etcd.service\nRequires=etcd.service\n\n[Service]\nType=simple\nUser=postgres\nGroup=postgres\nExecStart=\/usr\/bin\/patroni \/etc\/patroni\/patroni.yml\nExecReload=\/bin\/kill -s HUP $MAINPID\nKillMode=process\nTimeoutSec=30\nRestart=on-failure\nRestartSec=5\nLimitNOFILE=65536\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n<p>Before launching Patroni, stop and disable the standard PostgreSQL systemd service. Patroni must maintain exclusive control over spawning and terminating the PostgreSQL daemon:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl stop postgresql\nsudo systemctl disable postgresql\nsudo systemctl daemon-reload\nsudo systemctl enable --now patroni<\/code><\/pre>\n<p>Verify cluster status from any node using the <code>patronictl<\/code> CLI:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>patronictl -c \/etc\/patroni\/patroni.yml list<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Automated Self-Healing via pg_rewind:<\/strong> Note the parameter <code>use_pg_rewind: true<\/code>. When an old primary rejoins after a failover, its write-ahead log (WAL) might have diverged from the newly elected leader. Rather than forcing a costly and bandwidth-heavy full <code>pg_basebackup<\/code>, Patroni executes <code>pg_rewind<\/code> to identify the divergent point, rewind the uncommitted blocks, and switch into a streaming standby in seconds.<\/p>\n<\/blockquote>\n<h2>Step 4: PgBouncer Integration and Connection Virtualization<\/h2>\n<p>PostgreSQL processes connections using a traditional process-fork model. Each client socket consumes roughly 5MB to 10MB of RAM and incurs kernel context-switching overhead. Attempting to support thousands of simultaneous web application connections directly against PostgreSQL degrades throughput exponentially. Deploying PgBouncer in transaction pooling mode allows tens of thousands of client threads to share a lean, highly optimized pool of 30 to 50 active PostgreSQL server connections.<\/p>\n<p>Deploy PgBouncer on each node (or on a dedicated load balancing tier) with the following production configuration at <code>\/etc\/pgbouncer\/pgbouncer.ini<\/code>:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/pgbouncer\/pgbouncer.ini\n[databases]\n# Dynamic routing target updated via local Patroni REST API or HAProxy\napp_production = host=127.0.0.1 port=5432 dbname=app_production auth_user=postgres\n\n[pgbouncer]\nlogfile = \/var\/log\/postgresql\/pgbouncer.log\npidfile = \/var\/run\/postgresql\/pgbouncer.pid\nlisten_addr = 0.0.0.0\nlisten_port = 6432\nauth_type = scram-sha-256\nauth_file = \/etc\/pgbouncer\/userlist.txt\nadmin_users = postgres,sysadmin\nstats_users = monitoring\n\n# Connection Pool Architecture\npool_mode = transaction\nmax_client_conn = 10000\ndefault_pool_size = 40\nmin_pool_size = 10\nreserve_pool_size = 10\nreserve_pool_timeout = 5\nmax_db_connections = 100\n\n# Client and Server Connection Health\nserver_reset_query = DISCARD ALL\nserver_check_delay = 10\nserver_check_query = SELECT 1\nserver_lifetime = 3600\nserver_idle_timeout = 600\nclient_idle_timeout = 0\nquery_timeout = 120\n\n# Socket buffer optimizations\nso_reuseport = 1\ntcp_keepalive = 1\ntcp_keepidle = 30\ntcp_keepintvl = 10\ntcp_keepcnt = 3<\/code><\/pre>\n<p>Generate the authentication file <code>\/etc\/pgbouncer\/userlist.txt<\/code> using SCRAM-SHA-256 password hashes extracted directly from PostgreSQL:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/pgbouncer\/userlist.txt\n\"app_user\" \"SCRAM-SHA-256$4096:5a9G...==:7bF2...==\"\n\"postgres\" \"SCRAM-SHA-256$4096:2xM9...==:8cV1...==\"<\/code><\/pre>\n<p>Enable and start PgBouncer:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code>sudo systemctl enable --now pgbouncer\npsql -p 6432 -U postgres -d pgbouncer -c \"SHOW POOLS;\"<\/code><\/pre>\n<h2>Step 5: Client Traffic Steering via Patroni REST Endpoints<\/h2>\n<p>To steer client write queries strictly to the active leader and read queries to standbys, modern load balancers (such as HAProxy, Keepalived, or Envoy) leverage Patroni&#8217;s embedded HTTP REST service (port <code>8008<\/code>). Patroni responds with specific HTTP status codes based on the node&#8217;s cluster role:<\/p>\n<ul>\n<li><code>GET http:\/\/&lt;node-ip&gt;:8008\/primary<\/code> returns <strong>HTTP 200 OK<\/strong> only if the node is currently the active cluster leader holding the etcd lease. If the node is a standby, it returns <strong>HTTP 503 Service Unavailable<\/strong>.<\/li>\n<li><code>GET http:\/\/&lt;node-ip&gt;:8008\/replica<\/code> returns <strong>HTTP 200 OK<\/strong> if the node is an active, streaming read-only replica.<\/li>\n<\/ul>\n<p>Here is an example HAProxy backend configuration directing writes to the active Patroni leader with instantaneous failover detection:<\/p>\n<pre class=\"wp-block-code\" style=\"background:#f3f3f3;color:#333;padding:16px;border-left:4px solid #001b41;font-family:monospace;font-size:13px\"><code># \/etc\/haproxy\/haproxy.cfg\nfrontend pg_cluster_write_front\n    bind *:5000\n    mode tcp\n    option tcplog\n    default_backend pg_cluster_write_back\n\nbackend pg_cluster_write_back\n    mode tcp\n    option httpchk GET \/primary\n    http-check expect status 200\n    default-server inter 2s fall 2 rise 2 on-marked-down shutdown-sessions\n    server pg-node01 10.0.0.11:6432 maxconn 5000 check port 8008\n    server pg-node02 10.0.0.12:6432 maxconn 5000 check port 8008\n    server pg-node03 10.0.0.13:6432 maxconn 5000 check port 8008<\/code><\/pre>\n<blockquote class=\"wp-block-quote\" style=\"background:#f9f9f9;border-left:4px solid #001b41;padding:16px 20px;margin:24px 0\">\n<p><strong style=\"color:#001b41\">Production Failover Verification:<\/strong> You can simulate a sudden primary loss by executing <code>sudo systemctl stop patroni<\/code> on <code>pg-node01<\/code>. Within 10 seconds, the etcd lease expires, <code>pg-node02<\/code> or <code>pg-node03<\/code> promotes itself, HAProxy detects the new HTTP 200 response on port 8008, and all pooled application queries resume on the new master without a single connection dropped at the application layer.<\/p>\n<\/blockquote>\n<h2>Production Hosting Considerations<\/h2>\n<p>While testing multi-node PostgreSQL HA architectures on staging environments like <a href=\"https:\/\/cpanelfree.com\">CpanelFree<\/a> is ideal for configuration validation, production database workloads demand deterministic storage latency. PostgreSQL write-ahead logging (WAL) requires fsync operations that are fundamentally constrained by disk I\/O performance.<\/p>\n<p>For mission-critical production database clusters requiring guaranteed IOPS without shared-tenant performance jitter, host your infrastructure on <a href=\"https:\/\/merahost.org\" target=\"_blank\" rel=\"noopener\">MeraHost Enterprise Cloud<\/a>. MeraHost provides enterprise-grade dedicated NVMe storage tiers, unthrottled CPU cores, and an industry-leading Same Renewal Price guarantee with zero price hikes since 2012.<\/p>\n<h2>Frequently Asked Questions<\/h2>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">What happens when the Patroni leader node loses connectivity to etcd?<\/summary>\n<p style=\"margin-top:10px;color:#444\">If the current primary node experiences a network partition and cannot renew its leader lease key in etcd before the lease TTL expires (typically 30 seconds), Patroni initiates a graceful self-demotion. It immediately terminates write access and demotes the local PostgreSQL engine into a read-only state. This strict lease renewal enforcement guarantees split-brain immunity even under severe network isolation.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Why is PgBouncer transaction pooling preferred over session pooling in HA architectures?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Session pooling holds an open server connection for the entire duration of a client connection, meaning 1,000 idle application threads require 1,000 backend PostgreSQL connections. In contrast, transaction pooling releases the backend PostgreSQL connection back to the shared pool immediately upon transaction completion (COMMIT or ROLLBACK). This allows thousands of web workers to multiplex across as few as 20\u201350 PostgreSQL connections, maximizing throughput and caching efficiency.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">Can I run Patroni and etcd on a 2-node cluster without a third node?<\/summary>\n<p style=\"margin-top:10px;color:#444\">No. A 2-node etcd cluster cannot achieve a quorum majority if either node becomes unreachable (quorum formula: <code>(2\/2) + 1 = 2<\/code> votes needed). If one node fails, the surviving node has only 1 vote and will refuse all writes to prevent split-brain. You must deploy at least 3 nodes (or 2 database nodes plus a lightweight third etcd arbiter\/witness node) to maintain automated high availability.<\/p>\n<\/details>\n<details class=\"wp-block-group\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:4px;padding:14px;margin-bottom:12px\">\n<summary style=\"cursor:pointer;font-weight:600;color:#001b41\">How does Patroni handle replication lag during automated failover?<\/summary>\n<p style=\"margin-top:10px;color:#444\">Patroni tracks the Log Sequence Number (LSN) of each standby node within the DCS. When electing a new leader, the cluster enforces the <code>maximum_lag_on_failover<\/code> threshold (configured in bytes). Standbys whose replication lag exceeds this threshold are automatically disqualified from promotion, ensuring that only the replica with the most up-to-date WAL state is promoted to prevent data loss.<\/p>\n<\/details>\n<div class=\"wp-block-group has-background\" style=\"background:#f9f9f9;border:1px solid #e7e7e7;border-radius:8px;padding:32px;margin:40px 0;text-align:center\">\n<h3 style=\"color:#001b41;margin-top:0;font-size:24px;font-weight:700\">Deploy Enterprise-Grade Production Infrastructure<\/h3>\n<p style=\"color:#444;font-size:16px;line-height:1.6;max-width:680px;margin:12px auto 24px auto\">Need guaranteed performance with zero price hikes? Host mission-critical workloads on <strong style=\"color:#001b41\">MeraHost<\/strong> with pure Enterprise NVMe, LiteSpeed Web Server, and Same Renewal Price, Always (starting at \u20b999\/mo).<\/p>\n<div class=\"wp-block-buttons\" style=\"display:flex;gap:16px;justify-content:center;flex-wrap:wrap\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link\" href=\"https:\/\/merahost.org\" style=\"background:#001b41;color:#ffffff;font-weight:700;padding:12px 28px;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\" target=\"_blank\" rel=\"noopener\">Explore MeraHost NVMe Cloud &rarr;<\/a><\/div>\n<div class=\"wp-block-button is-style-outline\"><a class=\"wp-block-button__link\" href=\"https:\/\/cpanelfree.com\" style=\"background:transparent;color:#001b41;font-weight:600;padding:12px 24px;border:2px solid #001b41;border-radius:4px;text-decoration:none;display:inline-block;font-size:15px\">Deploy Free Staging on CpanelFree<\/a><\/div>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Deploy an automated PostgreSQL HA cluster with Patroni, etcd, and PgBouncer on Linux VPS. Master consensus failover and client pooling for zero downtime.<\/p>\n","protected":false},"author":1,"featured_media":5142,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[249],"tags":[57,250,177,87,101],"class_list":["post-5143","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-database-administration","tag-almalinux","tag-database-administration","tag-databases-performance","tag-devops","tag-sysadmin"],"_links":{"self":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/5143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/comments?post=5143"}],"version-history":[{"count":0,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/posts\/5143\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media\/5142"}],"wp:attachment":[{"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/media?parent=5143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/categories?post=5143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cpanelfree.com\/blog\/wp-json\/wp\/v2\/tags?post=5143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}