Configure PostgreSQL 17 streaming replication with automatic failover, PgBouncer connection pooling for performance optimization, and HAProxy load balancing for high availability database architecture.
Prerequisites
- Root or sudo access
- At least 2 servers with 4GB RAM each
- Network connectivity between servers
- Basic PostgreSQL knowledge
What this solves
PostgreSQL streaming replication with PgBouncer connection pooling creates a high-availability database setup that can handle read/write splitting and automatic failover. This configuration provides connection pooling to reduce database load, read replicas for performance scaling, and load balancing for optimal resource utilization.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you get the latest versions of all components.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget ca-certificates
Install PostgreSQL 17
Install PostgreSQL 17 from the official repository to get the latest features and performance improvements.
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install -y postgresql-17 postgresql-contrib-17
Initialize PostgreSQL on primary server
Initialize the PostgreSQL database cluster on your primary server. This creates the initial database structure and configuration files.
sudo systemctl start postgresql
sudo systemctl enable postgresql
Configure primary PostgreSQL server
Configure the primary server for streaming replication by enabling WAL archiving and setting replication parameters.
# Connection and memory settings
listen_addresses = '*'
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
WAL settings for replication
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GB
archive_mode = on
archive_command = 'cp %p /var/lib/postgresql/17/archive/%f'
Streaming replication settings
hot_standby = on
wal_receiver_timeout = 60s
wal_sender_timeout = 60s
Create archive directory
Create the WAL archive directory with proper permissions for PostgreSQL to store transaction logs.
sudo mkdir -p /var/lib/postgresql/17/archive
sudo chown postgres:postgres /var/lib/postgresql/17/archive
sudo chmod 700 /var/lib/postgresql/17/archive
Configure authentication for replication
Set up authentication rules to allow replication connections from replica servers.
# Database administrative login by Unix domain socket
local all postgres peer
TYPE DATABASE USER ADDRESS METHOD
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
Replication connections
host replication replica 203.0.113.0/24 scram-sha-256
host all pgbouncer 203.0.113.0/24 scram-sha-256
Create replication user
Create a dedicated user for streaming replication with appropriate permissions.
sudo -u postgres psql -c "CREATE ROLE replica REPLICATION LOGIN PASSWORD 'your_secure_password';"
sudo -u postgres psql -c "CREATE ROLE pgbouncer LOGIN PASSWORD 'pgbouncer_password';"
Restart primary PostgreSQL server
Restart the PostgreSQL service to apply the configuration changes.
sudo systemctl restart postgresql
Set up replica server
On your replica server, install PostgreSQL and create a base backup from the primary server.
sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/17/main/*
sudo -u postgres pg_basebackup -h 203.0.113.10 -D /var/lib/postgresql/17/main -U replica -v -P -W -R
sudo systemctl start postgresql
Install PgBouncer
Install PgBouncer connection pooler to manage database connections efficiently and reduce connection overhead.
sudo apt install -y pgbouncer
Configure PgBouncer on primary server
Configure PgBouncer to pool connections to the primary PostgreSQL server for write operations.
[databases]
* = host=127.0.0.1 port=5432
[pgbouncer]
listen_addr = *
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
max_db_connections = 100
reserve_pool_size = 5
reserve_pool_timeout = 3
server_reset_query = DISCARD ALL
server_check_delay = 30
server_check_query = SELECT 1
server_lifetime = 3600
server_idle_timeout = 600
log_connections = 1
log_disconnections = 1
stats_period = 60
pid_file = /var/run/postgresql/pgbouncer.pid
logfile = /var/log/postgresql/pgbouncer.log
unix_socket_dir = /var/run/postgresql
Configure PgBouncer authentication
Create the user authentication file for PgBouncer with encrypted passwords.
sudo -u postgres psql -c "SELECT usename, passwd FROM pg_shadow WHERE usename = 'pgbouncer';" | sudo tee /etc/pgbouncer/userlist.txt
sudo chown pgbouncer:pgbouncer /etc/pgbouncer/userlist.txt
sudo chmod 600 /etc/pgbouncer/userlist.txt
Configure PgBouncer on replica server
Set up PgBouncer on the replica server for read-only connections with appropriate configuration.
[databases]
* = host=127.0.0.1 port=5432
[pgbouncer]
listen_addr = *
listen_port = 6433
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
max_db_connections = 100
reserve_pool_size = 5
reserve_pool_timeout = 3
server_reset_query = DISCARD ALL
server_check_delay = 30
server_check_query = SELECT 1
server_lifetime = 3600
server_idle_timeout = 600
log_connections = 1
log_disconnections = 1
stats_period = 60
pid_file = /var/run/postgresql/pgbouncer.pid
logfile = /var/log/postgresql/pgbouncer.log
unix_socket_dir = /var/run/postgresql
Install and configure HAProxy
Install HAProxy to provide load balancing and automatic failover between primary and replica servers.
sudo apt install -y haproxy
Configure HAProxy load balancing
Set up HAProxy to route write operations to the primary server and read operations to replicas with health checks.
global
daemon
maxconn 4096
log stdout local0
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
defaults
mode tcp
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
option tcplog
log global
listen stats
bind *:8404
stats enable
stats uri /stats
stats refresh 30s
stats hide-version
frontend postgresql_write
bind *:5432
default_backend postgresql_primary
frontend postgresql_read
bind *:5433
default_backend postgresql_replicas
backend postgresql_primary
balance roundrobin
option pgsql-check user pgbouncer
server primary 203.0.113.10:6432 check port 6432
server replica 203.0.113.11:6433 check port 6433 backup
backend postgresql_replicas
balance roundrobin
option pgsql-check user pgbouncer
server replica1 203.0.113.11:6433 check port 6433
server primary 203.0.113.10:6432 check port 6432 backup
Start all services
Enable and start PgBouncer and HAProxy services on their respective servers.
sudo systemctl enable --now pgbouncer
sudo systemctl enable --now haproxy
Configure firewall rules
Open the necessary ports for PostgreSQL replication, PgBouncer, and HAProxy communication.
sudo ufw allow 5432/tcp
sudo ufw allow 6432/tcp
sudo ufw allow 6433/tcp
sudo ufw allow 8404/tcp
Verify your setup
# Check replication status on primary
sudo -u postgres psql -c "SELECT client_addr, state, sync_state FROM pg_stat_replication;"
Check replica status
sudo -u postgres psql -c "SELECT pg_is_in_recovery();"
Test PgBouncer connections
psql -h localhost -p 6432 -U postgres -c "SHOW POOLS;"
Check HAProxy stats
curl -s http://localhost:8404/stats
Test write connection through HAProxy
psql -h localhost -p 5432 -U postgres -c "SELECT now();"
Test read connection through HAProxy
psql -h localhost -p 5433 -U postgres -c "SELECT now();"
Monitoring replication health
Set up monitoring queries to track replication lag and connection pool status.
# Monitor replication lag
sudo -u postgres psql -c "SELECT EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds;"
Check PgBouncer pool statistics
psql -h localhost -p 6432 -U pgbouncer pgbouncer -c "SHOW STATS;"
Monitor active connections
sudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity WHERE state = 'active';"
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Replication not starting | Authentication failure | Check pg_hba.conf and replica user password |
| PgBouncer connection refused | Wrong listen address or port | Verify listen_addr and listen_port in pgbouncer.ini |
| HAProxy backend down | Health check failing | Check PostgreSQL and PgBouncer are running on target servers |
| High replication lag | Network or disk I/O bottleneck | Check network connectivity and disk performance |
| Connection pool exhaustion | Too many concurrent connections | Increase default_pool_size or optimize application connection usage |
| Failover not working | Health check misconfiguration | Verify HAProxy health check settings and user permissions |
Performance optimization
Fine-tune your setup for optimal performance based on your workload characteristics and server resources.
Optimize PostgreSQL settings
Adjust PostgreSQL configuration for your specific hardware and workload requirements.
# Memory settings (adjust based on available RAM)
shared_buffers = 25% of RAM
effective_cache_size = 75% of RAM
work_mem = Available RAM / max_connections / 4
maintenance_work_mem = Available RAM / 16
Connection settings
max_connections = 200
superuser_reserved_connections = 3
WAL settings for performance
wal_compression = on
wal_buffers = 16MB
checkpoint_completion_target = 0.9
max_wal_size = 2GB
min_wal_size = 1GB
Optimize PgBouncer pools
Tune PgBouncer pool sizes based on your application connection patterns and PostgreSQL capacity.
# Pool configuration
default_pool_size = max_connections / 4
max_db_connections = max_connections * 0.8
reserve_pool_size = default_pool_size / 5
Connection lifetime settings
server_lifetime = 3600
server_idle_timeout = 600
client_idle_timeout = 0
Next steps
- Monitor PostgreSQL performance with Prometheus and Grafana dashboards
- Implement automated PostgreSQL backups with pgBackRest
- Configure PostgreSQL SSL encryption and security hardening
- Set up PostgreSQL connection pooling with PgPool-II
- Implement PostgreSQL point-in-time recovery
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Usage function
usage() {
echo "Usage: $0 [primary|replica] [primary_ip] [replica_password] [pgbouncer_password]"
echo " primary - Set up primary PostgreSQL server"
echo " replica - Set up replica PostgreSQL server"
echo " primary_ip - IP address of primary server (required for replica)"
echo " replica_password - Password for replication user"
echo " pgbouncer_password - Password for pgbouncer user"
exit 1
}
# Error cleanup
cleanup() {
echo -e "${RED}[ERROR] Installation failed. Check logs for details.${NC}"
exit 1
}
trap cleanup ERR
# Check prerequisites
check_prereqs() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
}
# Detect distribution
detect_distro() {
if [[ ! -f /etc/os-release ]]; then
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
PG_SERVICE="postgresql"
PG_DATA_DIR="/var/lib/postgresql/17/main"
PG_CONFIG_DIR="/etc/postgresql/17/main"
PG_BIN_DIR="/usr/lib/postgresql/17/bin"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PG_SERVICE="postgresql-17"
PG_DATA_DIR="/var/lib/pgsql/17/data"
PG_CONFIG_DIR="/var/lib/pgsql/17/data"
PG_BIN_DIR="/usr/pgsql-17/bin"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PG_SERVICE="postgresql"
PG_DATA_DIR="/var/lib/pgsql/data"
PG_CONFIG_DIR="/var/lib/pgsql/data"
PG_BIN_DIR="/usr/bin"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PG_SERVICE="postgresql"
PG_DATA_DIR="/var/lib/pgsql/data"
PG_CONFIG_DIR="/var/lib/pgsql/data"
PG_BIN_DIR="/usr/bin"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
}
# Install PostgreSQL 17
install_postgresql() {
echo -e "${GREEN}[2/10] Installing PostgreSQL 17...${NC}"
case "$ID" in
ubuntu|debian)
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt update
$PKG_INSTALL postgresql-17 postgresql-contrib-17 pgbouncer
;;
almalinux|rocky|centos|rhel|ol)
$PKG_INSTALL https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
$PKG_INSTALL postgresql17-server postgresql17-contrib pgbouncer
;;
esac
}
# Initialize PostgreSQL
init_postgresql() {
echo -e "${GREEN}[3/10] Initializing PostgreSQL...${NC}"
case "$ID" in
ubuntu|debian)
systemctl start postgresql
systemctl enable postgresql
;;
almalinux|rocky|centos|rhel|ol)
$PG_BIN_DIR/postgresql-17-setup initdb
systemctl enable --now postgresql-17
;;
esac
}
# Configure primary server
configure_primary() {
echo -e "${GREEN}[4/10] Configuring primary server...${NC}"
# Create archive directory
mkdir -p $PG_DATA_DIR/archive
chown postgres:postgres $PG_DATA_DIR/archive
chmod 700 $PG_DATA_DIR/archive
# Configure postgresql.conf
cat >> $PG_CONFIG_DIR/postgresql.conf << EOF
# Replication configuration
listen_addresses = '*'
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10
wal_keep_size = 1GB
archive_mode = on
archive_command = 'cp %p $PG_DATA_DIR/archive/%f'
hot_standby = on
wal_receiver_timeout = 60s
wal_sender_timeout = 60s
EOF
# Configure pg_hba.conf
cat >> $PG_CONFIG_DIR/pg_hba.conf << EOF
host replication replica 0.0.0.0/0 scram-sha-256
host all pgbouncer 0.0.0.0/0 scram-sha-256
EOF
}
# Create users
create_users() {
echo -e "${GREEN}[5/10] Creating replication users...${NC}"
systemctl restart $PG_SERVICE
sleep 5
sudo -u postgres psql -c "CREATE ROLE replica REPLICATION LOGIN PASSWORD '$REPLICA_PASS';" || true
sudo -u postgres psql -c "CREATE ROLE pgbouncer LOGIN PASSWORD '$PGBOUNCER_PASS';" || true
}
# Configure replica server
configure_replica() {
echo -e "${GREEN}[4/10] Configuring replica server...${NC}"
systemctl stop $PG_SERVICE
rm -rf $PG_DATA_DIR/*
# Create base backup
sudo -u postgres PGPASSWORD=$REPLICA_PASS $PG_BIN_DIR/pg_basebackup -h $PRIMARY_IP -D $PG_DATA_DIR -U replica -v -P -W
chown -R postgres:postgres $PG_DATA_DIR
chmod 700 $PG_DATA_DIR
# Create standby.signal
touch $PG_DATA_DIR/standby.signal
chown postgres:postgres $PG_DATA_DIR/standby.signal
# Configure primary_conninfo
cat >> $PG_CONFIG_DIR/postgresql.conf << EOF
primary_conninfo = 'host=$PRIMARY_IP port=5432 user=replica password=$REPLICA_PASS'
EOF
}
# Configure PgBouncer
configure_pgbouncer() {
echo -e "${GREEN}[6/10] Configuring PgBouncer...${NC}"
cat > /etc/pgbouncer/pgbouncer.ini << EOF
[databases]
* = host=127.0.0.1 port=5432
[pgbouncer]
listen_port = 6432
listen_addr = *
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
admin_users = pgbouncer
pool_mode = session
server_reset_query = DISCARD ALL
max_client_conn = 1000
default_pool_size = 50
EOF
echo "\"pgbouncer\" \"$PGBOUNCER_PASS\"" > /etc/pgbouncer/userlist.txt
chown pgbouncer:pgbouncer /etc/pgbouncer/userlist.txt
chmod 640 /etc/pgbouncer/userlist.txt
systemctl enable pgbouncer
systemctl start pgbouncer
}
# Configure firewall
configure_firewall() {
echo -e "${GREEN}[7/10] Configuring firewall...${NC}"
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=5432/tcp
firewall-cmd --permanent --add-port=6432/tcp
firewall-cmd --reload
elif command -v ufw &> /dev/null; then
ufw allow 5432/tcp
ufw allow 6432/tcp
fi
}
# Verify installation
verify_installation() {
echo -e "${GREEN}[8/10] Verifying installation...${NC}"
if systemctl is-active --quiet $PG_SERVICE; then
echo -e "${GREEN}PostgreSQL is running${NC}"
else
echo -e "${RED}PostgreSQL is not running${NC}"
exit 1
fi
if systemctl is-active --quiet pgbouncer; then
echo -e "${GREEN}PgBouncer is running${NC}"
else
echo -e "${YELLOW}PgBouncer is not running${NC}"
fi
}
# Main function
main() {
if [[ $# -lt 4 ]]; then
usage
fi
MODE=$1
PRIMARY_IP=${2:-""}
REPLICA_PASS=$3
PGBOUNCER_PASS=$4
check_prereqs
echo -e "${GREEN}[1/10] Updating system packages...${NC}"
detect_distro
$PKG_UPDATE
$PKG_INSTALL wget ca-certificates lsb-release
install_postgresql
case "$MODE" in
"primary")
init_postgresql
configure_primary
create_users
configure_pgbouncer
;;
"replica")
if [[ -z "$PRIMARY_IP" ]]; then
echo -e "${RED}Primary IP is required for replica setup${NC}"
exit 1
fi
init_postgresql
configure_replica
systemctl start $PG_SERVICE
configure_pgbouncer
;;
*)
usage
;;
esac
configure_firewall
verify_installation
echo -e "${GREEN}[9/10] PostgreSQL 17 with PgBouncer setup complete!${NC}"
echo -e "${GREEN}[10/10] PostgreSQL: port 5432, PgBouncer: port 6432${NC}"
}
main "$@"
Review the script before running. Execute with: bash install.sh