Connection Proxies
During failover, something must send client connections to the new primary. HAProxy (or a cloud load balancer) routes TCP; PgBouncer 1.x pools sessions. They solve different layers.
Recipe
HAProxy TCP frontends for write (primary) and read (replicas); PgBouncer in transaction pool mode for app workers.
# HAProxy backend checks Patroni REST (port 8008) for leader
# frontend pg_write -> backend patroni_primary (only leader passes check)
# frontend pg_read -> backend patroni_replicas (standbys pass replica check)# pgbouncer.ini snippet
[databases]
appdb = host=pg-write.internal port=5432 dbname=appdb
[pgbouncer]
listen_port = 6432
pool_mode = transaction
max_client_conn = 2000
default_pool_size = 50
server_lifetime = 300When to reach for this: More than a handful of app servers connecting to PostgreSQL; failover must not require app config changes.
Working Example
HAProxy Patroni check script and PgBouncer reload after failover:
#!/usr/bin/env bash
# patroni-primary-check.sh - exit 0 if this node is Patroni leader
LEADER=$(curl -sf http://127.0.0.1:8008/patroni | jq -r .role)
[[ "$LEADER" == "master" || "$LEADER" == "primary" ]]# haproxy.cfg excerpt
backend patroni_primary
mode tcp
option tcp-check
tcp-check connect port 8008
tcp-check send GET\ /primary\ HTTP/1.0\r\n
tcp-check expect string 200
server pg1 10.0.1.11:5432 check port 8008
server pg2 10.0.1.12:5432 check port 8008
server pg3 10.0.1.13:5432 check port 8008# After failover: bounce PgBouncer to drop stale server connections
psql -h 127.0.0.1 -p 6432 -U pgbouncer pgbouncer -c "RECONNECT;"
# or: systemctl reload pgbouncerWhat this demonstrates:
- HAProxy uses Patroni REST
/primaryand/replicaendpoints for health routing. - Apps connect to
pg-write.internal:6432(PgBouncer) not individual node IPs. RECONNECTor reload clears pools pointing at old primary.
Deep Dive
Layer Responsibilities
| Component | OSI layer | Failover behavior |
|---|---|---|
| HAProxy / NLB | TCP (L4) | Routes to healthy Patroni leader |
| PgBouncer 1.x | Connection pool | Reopens backends after leader change |
| RDS Proxy | Managed pool + IAM | AWS handles backend swap |
| App JDBC URL | Client | Must use stable hostname + timeouts |
Read/Write Split
- Writes: Single backend that passes Patroni primary check.
- Reads: Replicas via
/replicacheck; tolerate slight lag for reporting. - Caution: Read-your-writes requires primary or sync-aware routing, not any replica.
PgBouncer Modes
| Mode | HA note |
|---|---|
session | Safer for prepared statements; fewer connections saved |
transaction | Common for web apps; use DISCARD ALL compatibility |
statement | Rare; breaks many ORM patterns |
Gotchas
- PgBouncer only, no HAProxy - Pool still aims at one hostname; DNS must update on failover. Fix: Patroni + HAProxy or reliable DNS TTL + health checks.
- Prepared statements in transaction mode - Errors after pool reuse. Fix:
pool_mode = sessionfor affected apps or disable prepared statements. - HAProxy checking PostgreSQL port only - Both primary and replica accept TCP; wrong routing. Fix: Patroni HTTP health checks on 8008.
- Long
server_lifetime- Stale primary connections for minutes. Fix: Lower lifetime;RECONNECTin Patroni on_role_change callback. - RDS Proxy with self-hosted Patroni - Different products; do not mix patterns. Fix: Pick managed or self-hosted stack per environment.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| VIP + keepalived | Legacy datacenter | Cloud LB preferred |
| Kubernetes Service + Patroni endpoints | K8s-hosted PG | Bare-metal VMs |
| Direct DNS CNAME swap | Simplest small setups | Sub-minute RTO required |
FAQs
HAProxy vs PgBouncer order?
Common: client -> PgBouncer -> HAProxy -> PostgreSQL, or client -> HAProxy -> PgBouncer -> PostgreSQL. Pick one topology and document it.
Does RDS Proxy replace Patroni?
No. RDS Proxy pools connections; Aurora/RDS Multi-AZ handles failover underneath.
Replication connections through PgBouncer?
No. Streaming and logical replication connect directly to PostgreSQL port 5432.
SSL termination?
Terminate at HAProxy or use sslmode=require end-to-end. Match compliance requirements.
How many PgBouncer instances?
At least two for HA; each points at same HAProxy write VIP.
Related
- Patroni & etcd/Consul - leader election
- HA Basics - client retry requirements
- HA Testing - validate routing in drills
- Connection Math - pool sizing
Stack versions: This page was written for PostgreSQL 18.4 (stable 18, maintenance 17), pgvector 0.8+, PgBouncer 1.x, Patroni 3.x, and PostGIS 3.5+.