Learn to configure PgBouncer connection pooling to optimize PostgreSQL performance, reduce connection overhead, and handle thousands of concurrent database connections efficiently in production environments.
Prerequisites
- PostgreSQL server installed and running
- Sudo access to install packages
- Basic knowledge of SQL and database concepts
- At least 2GB RAM available for optimal performance
What this solves
PostgreSQL has a process-per-connection architecture that can become a bottleneck under high concurrent loads. Each database connection consumes significant memory (typically 2-10MB) and creates overhead for connection establishment and management. PgBouncer solves this by maintaining a pool of persistent database connections and multiplexing client requests, allowing applications to handle thousands of concurrent users with minimal database connections.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all packages.
sudo apt update && sudo apt upgrade -y
Install PgBouncer
Install PgBouncer from the official repositories. This includes the main daemon and configuration tools.
sudo apt install -y pgbouncer postgresql-client
Create PgBouncer user and directories
Create a dedicated system user for PgBouncer and set up the required directory structure with proper permissions.
sudo useradd --system --home /var/lib/pgbouncer --shell /bin/bash pgbouncer
sudo mkdir -p /var/lib/pgbouncer /var/log/pgbouncer /etc/pgbouncer
sudo chown pgbouncer:pgbouncer /var/lib/pgbouncer /var/log/pgbouncer
sudo chmod 750 /var/lib/pgbouncer /var/log/pgbouncer
Configure PostgreSQL for connection pooling
Optimize PostgreSQL settings to work efficiently with PgBouncer. These settings ensure proper connection handling and performance.
# Connection settings for PgBouncer optimization
max_connections = 200
shared_buffers = 256MB
effective_cache_size = 1GB
Reduce connection overhead
tcp_keepalives_idle = 600
tcp_keepalives_interval = 30
tcp_keepalives_count = 3
Statement timeout to prevent long-running queries
statement_timeout = 300000
idle_in_transaction_session_timeout = 60000
Create authentication file
Set up the userlist.txt file containing database users and their password hashes. PgBouncer uses this for client authentication.
sudo touch /etc/pgbouncer/userlist.txt
sudo chown pgbouncer:pgbouncer /etc/pgbouncer/userlist.txt
sudo chmod 600 /etc/pgbouncer/userlist.txt
"myappuser" "md5d8578edf8458ce06fbc5bb76a58c5ca4"
"readonly_user" "md5a1b2c3d4e5f6789012345678901234ab"
echo -n 'passwordusername' | md5sum. For user 'myapp' with password 'securepass123', run: echo -n 'securepass123myapp' | md5sum.Configure PgBouncer main settings
Create the primary PgBouncer configuration file with optimized settings for high-traffic applications.
[databases]
myapp_db = host=127.0.0.1 port=5432 dbname=myapp_production user=myappuser
readonly_db = host=127.0.0.1 port=5432 dbname=myapp_production user=readonly_user
[pgbouncer]
Connection pooling settings
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
max_db_connections = 50
Authentication
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
Network settings
listen_addr = *
listen_port = 6432
unix_socket_dir = /var/run/postgresql
Logging
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
Performance tuning
server_lifetime = 3600
server_idle_timeout = 600
client_idle_timeout = 0
query_timeout = 300
query_wait_timeout = 120
client_login_timeout = 60
Admin settings
admin_users = pgbouncer
stats_users = pgbouncer
Create systemd service file
Set up a systemd service to manage PgBouncer with proper security settings and automatic restart capability.
[Unit]
Description=PgBouncer connection pooler for PostgreSQL
Documentation=man:pgbouncer(1)
After=network.target postgresql.service
Requires=network.target
[Service]
Type=notify
User=pgbouncer
Group=pgbouncer
ExecStart=/usr/bin/pgbouncer /etc/pgbouncer/pgbouncer.ini
ExecReload=/bin/kill -HUP $MAINPID
KillSignal=SIGINT
TimeoutSec=300
Restart=always
RestartSec=5
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/pgbouncer /var/run/pgbouncer
Process limits
LimitNOFILE=65536
LimitNPROC=32768
[Install]
WantedBy=multi-user.target
Create runtime directory
Set up the runtime directory for PgBouncer PID files with proper ownership and permissions.
sudo mkdir -p /var/run/pgbouncer
sudo chown pgbouncer:pgbouncer /var/run/pgbouncer
sudo chmod 755 /var/run/pgbouncer
Configure log rotation
Set up log rotation to prevent PgBouncer logs from consuming too much disk space.
/var/log/pgbouncer/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
su pgbouncer pgbouncer
}
Optimize system limits
Increase system limits to handle high connection counts. These settings are essential for high-traffic applications.
pgbouncer soft nofile 65536
pgbouncer hard nofile 65536
pgbouncer soft nproc 32768
pgbouncer hard nproc 32768
Configure firewall rules
Open the PgBouncer port (6432) in your firewall to allow client connections.
sudo ufw allow 6432/tcp comment 'PgBouncer'
sudo ufw status
Enable and start services
Start PostgreSQL and PgBouncer services, enabling them to start automatically on boot.
sudo systemctl restart postgresql
sudo systemctl enable pgbouncer
sudo systemctl start pgbouncer
sudo systemctl status pgbouncer
Optimize pool settings for different workload types
Transaction pooling for OLTP workloads
For web applications with short transactions, use transaction pooling mode for maximum efficiency.
# OLTP/Web application settings
pool_mode = transaction
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
max_db_connections = 50
server_idle_timeout = 300
query_timeout = 120
Session pooling for complex applications
For applications using prepared statements, temporary tables, or session-specific settings, use session pooling.
# Session-based application settings
pool_mode = session
default_pool_size = 50
min_pool_size = 10
reserve_pool_size = 10
max_db_connections = 100
server_lifetime = 1800
server_idle_timeout = 600
Statement pooling for simple queries
For read-heavy applications with simple SELECT queries, statement pooling provides the highest connection efficiency.
# Read-heavy workload settings
pool_mode = statement
default_pool_size = 10
min_pool_size = 2
reserve_pool_size = 3
max_db_connections = 20
server_idle_timeout = 600
query_timeout = 60
Monitor connection pool performance
Access PgBouncer admin console
Connect to PgBouncer's built-in admin console to monitor pool statistics and performance metrics.
psql -h localhost -p 6432 -U pgbouncer pgbouncer
Password: same as configured in userlist.txt
Monitor key metrics
Use these admin commands to check pool health, connection usage, and identify bottlenecks.
-- Show pool statistics
SHOW POOLS;
-- Show client connections
SHOW CLIENTS;
-- Show server connections
SHOW SERVERS;
-- Show configuration
SHOW CONFIG;
-- Show statistics
SHOW STATS;
Set up automated monitoring
Create a monitoring script to track PgBouncer metrics and send alerts when thresholds are exceeded.
#!/bin/bash
PgBouncer monitoring script
HOST="localhost"
PORT="6432"
USER="pgbouncer"
DB="pgbouncer"
MAX_CLIENT_CONN=800
MAX_WAIT_TIME=30
Check client connections
CLIENT_COUNT=$(psql -h $HOST -p $PORT -U $USER -d $DB -t -c "SELECT SUM(cl_active + cl_waiting) FROM pgbouncer.pools;" 2>/dev/null | tr -d ' ')
if [ "$CLIENT_COUNT" -gt $MAX_CLIENT_CONN ]; then
echo "WARNING: High client connections: $CLIENT_COUNT" | logger -t pgbouncer-monitor
fi
Check for waiting clients
WAIT_COUNT=$(psql -h $HOST -p $PORT -U $USER -d $DB -t -c "SELECT SUM(cl_waiting) FROM pgbouncer.pools;" 2>/dev/null | tr -d ' ')
if [ "$WAIT_COUNT" -gt 10 ]; then
echo "CRITICAL: Clients waiting for connections: $WAIT_COUNT" | logger -t pgbouncer-monitor
fi
sudo chmod +x /usr/local/bin/pgbouncer-monitor.sh
sudo chown pgbouncer:pgbouncer /usr/local/bin/pgbouncer-monitor.sh
Configure performance alerts
Set up cron job to run monitoring checks every minute and log issues for further analysis.
sudo -u pgbouncer crontab -e
* /usr/local/bin/pgbouncer-monitor.sh
Verify your setup
Test your PgBouncer installation and verify that connection pooling is working correctly.
# Check PgBouncer status
sudo systemctl status pgbouncer
Test connection through PgBouncer
psql -h localhost -p 6432 -U myappuser -d myapp_db -c "SELECT version();"
Connect to admin console
psql -h localhost -p 6432 -U pgbouncer pgbouncer -c "SHOW POOLS;"
Check logs for any errors
sudo tail -f /var/log/pgbouncer/pgbouncer.log
Verify PostgreSQL is accepting connections
psql -h localhost -p 5432 -U myappuser -d myapp_production -c "SELECT current_database();"
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| PgBouncer won't start | Configuration syntax error | Check sudo journalctl -u pgbouncer -f and validate config syntax |
| Authentication failed | Incorrect password hash in userlist.txt | Regenerate MD5 hash: echo -n 'passwordusername' | md5sum |
| Clients waiting for connections | Pool size too small | Increase default_pool_size and max_db_connections |
| "server conn crashed" errors | PostgreSQL connection limits exceeded | Increase PostgreSQL max_connections setting |
| High query wait times | Pool mode mismatch | Use transaction mode for web apps, session mode for complex queries |
| Connection timeouts | Firewall blocking port 6432 | Open port: sudo ufw allow 6432/tcp or firewall-cmd --add-port=6432/tcp |
Next steps
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'
# Default values
DB_HOST="${1:-127.0.0.1}"
DB_PORT="${2:-5432}"
DB_NAME="${3:-myapp_production}"
DB_USER="${4:-myappuser}"
DB_PASSWORD="${5:-securepass123}"
# Usage function
usage() {
echo "Usage: $0 [DB_HOST] [DB_PORT] [DB_NAME] [DB_USER] [DB_PASSWORD]"
echo "Example: $0 127.0.0.1 5432 myapp_production myappuser securepass123"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function
cleanup() {
log_error "Installation failed. Cleaning up..."
systemctl stop pgbouncer 2>/dev/null || true
systemctl disable pgbouncer 2>/dev/null || true
userdel pgbouncer 2>/dev/null || true
rm -rf /var/lib/pgbouncer /var/log/pgbouncer /etc/pgbouncer 2>/dev/null || true
rm -f /etc/systemd/system/pgbouncer.service 2>/dev/null || true
systemctl daemon-reload 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Detect distribution and package manager
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
PGBOUNCER_PKG="pgbouncer postgresql-client"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PGBOUNCER_PKG="pgbouncer postgresql"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PGBOUNCER_PKG="pgbouncer postgresql"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution. /etc/os-release not found."
exit 1
fi
log_info "Detected distribution: $ID using $PKG_MGR"
# Step 1: Update system packages
echo "[1/8] Updating system packages..."
$PKG_UPDATE
# Step 2: Install PgBouncer
echo "[2/8] Installing PgBouncer..."
$PKG_INSTALL $PGBOUNCER_PKG
# Step 3: Create PgBouncer user and directories
echo "[3/8] Creating PgBouncer user and directories..."
if ! id "pgbouncer" &>/dev/null; then
useradd --system --home /var/lib/pgbouncer --shell /bin/bash pgbouncer
fi
mkdir -p /var/lib/pgbouncer /var/log/pgbouncer /etc/pgbouncer /var/run/pgbouncer
chown pgbouncer:pgbouncer /var/lib/pgbouncer /var/log/pgbouncer /var/run/pgbouncer
chmod 750 /var/lib/pgbouncer /var/log/pgbouncer /var/run/pgbouncer
chmod 755 /etc/pgbouncer
# Step 4: Create authentication file
echo "[4/8] Creating authentication file..."
touch /etc/pgbouncer/userlist.txt
chown pgbouncer:pgbouncer /etc/pgbouncer/userlist.txt
chmod 600 /etc/pgbouncer/userlist.txt
# Generate MD5 hash for password
MD5_HASH=$(echo -n "${DB_PASSWORD}${DB_USER}" | md5sum | cut -d' ' -f1)
cat > /etc/pgbouncer/userlist.txt << EOF
"${DB_USER}" "md5${MD5_HASH}"
"pgbouncer" "md5${MD5_HASH}"
EOF
# Step 5: Create PgBouncer configuration
echo "[5/8] Creating PgBouncer configuration..."
cat > /etc/pgbouncer/pgbouncer.ini << EOF
[databases]
${DB_NAME} = host=${DB_HOST} port=${DB_PORT} dbname=${DB_NAME} user=${DB_USER}
[pgbouncer]
; Connection pooling settings
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
min_pool_size = 5
reserve_pool_size = 5
max_db_connections = 50
; Authentication
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
; Network settings
listen_addr = *
listen_port = 6432
unix_socket_dir = /var/run/postgresql
; Logging
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
logfile = /var/log/pgbouncer/pgbouncer.log
pidfile = /var/run/pgbouncer/pgbouncer.pid
; Performance tuning
server_lifetime = 3600
server_idle_timeout = 600
client_idle_timeout = 0
query_timeout = 300
query_wait_timeout = 120
client_login_timeout = 60
; Admin settings
admin_users = pgbouncer
stats_users = pgbouncer
EOF
chown pgbouncer:pgbouncer /etc/pgbouncer/pgbouncer.ini
chmod 640 /etc/pgbouncer/pgbouncer.ini
# Step 6: Create systemd service
echo "[6/8] Creating systemd service..."
cat > /etc/systemd/system/pgbouncer.service << EOF
[Unit]
Description=PgBouncer connection pooler for PostgreSQL
Documentation=man:pgbouncer(1)
After=network.target postgresql.service
Requires=network.target
[Service]
Type=forking
User=pgbouncer
Group=pgbouncer
ExecStart=/usr/bin/pgbouncer -d /etc/pgbouncer/pgbouncer.ini
ExecReload=/bin/kill -HUP \$MAINPID
PIDFile=/var/run/pgbouncer/pgbouncer.pid
Restart=always
RestartSec=5
TimeoutSec=60
; Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/pgbouncer /var/log/pgbouncer /var/run/pgbouncer
[Install]
WantedBy=multi-user.target
EOF
# Step 7: Configure firewall
echo "[7/8] Configuring firewall..."
if command -v firewall-cmd &> /dev/null; then
firewall-cmd --permanent --add-port=6432/tcp || log_warn "Failed to add firewall rule"
firewall-cmd --reload || log_warn "Failed to reload firewall"
elif command -v ufw &> /dev/null; then
ufw allow 6432/tcp || log_warn "Failed to add UFW rule"
else
log_warn "No supported firewall detected. Please manually allow port 6432/tcp"
fi
# Configure SELinux if present
if command -v setsebool &> /dev/null; then
setsebool -P nis_enabled 1 || log_warn "Failed to set SELinux boolean"
fi
# Step 8: Start and enable services
echo "[8/8] Starting and enabling PgBouncer service..."
systemctl daemon-reload
systemctl enable pgbouncer
systemctl start pgbouncer
# Verification checks
echo ""
log_info "Performing verification checks..."
if systemctl is-active --quiet pgbouncer; then
log_info "✓ PgBouncer service is running"
else
log_error "✗ PgBouncer service is not running"
systemctl status pgbouncer
exit 1
fi
if netstat -tlnp 2>/dev/null | grep -q ":6432"; then
log_info "✓ PgBouncer is listening on port 6432"
else
log_warn "⚠ PgBouncer may not be listening on port 6432"
fi
if [ -f /var/log/pgbouncer/pgbouncer.log ]; then
log_info "✓ Log file created successfully"
else
log_warn "⚠ Log file not found"
fi
echo ""
log_info "PgBouncer installation completed successfully!"
echo ""
echo "Configuration details:"
echo " - Connection string: host=localhost port=6432 dbname=${DB_NAME} user=${DB_USER}"
echo " - Admin interface: psql -h localhost -p 6432 -U pgbouncer pgbouncer"
echo " - Config file: /etc/pgbouncer/pgbouncer.ini"
echo " - Log file: /var/log/pgbouncer/pgbouncer.log"
echo ""
echo "Next steps:"
echo " 1. Update your application connection string to use port 6432"
echo " 2. Monitor logs: tail -f /var/log/pgbouncer/pgbouncer.log"
echo " 3. Check stats: echo 'SHOW STATS;' | psql -h localhost -p 6432 -U pgbouncer pgbouncer"
Review the script before running. Execute with: bash install.sh