Building a robust, highly available PostgreSQL cluster is crucial for production applications that demand zero downtime and data consistency. This comprehensive tutorial will guide you through deploying a three-node PostgreSQL 16 cluster using Patroni for automated failover, etcd for distributed consensus, HAProxy for load balancing, and TLS encryption for secure communications.
By the end of this guide, you’ll have a production-ready PostgreSQL cluster that automatically handles primary failover, read-write splitting, and maintains data integrity across multiple nodes.
Prerequisites
Before starting this deployment, ensure you have:
- Three Ubuntu 24.04 LTS VPS instances with at least 2GB RAM and 20GB storage each
- Root or sudo access on all three servers
- Network connectivity between all nodes (private networking preferred)
- Basic understanding of PostgreSQL, Linux administration, and database clustering concepts
- Domain names or static IPs for each node
System Requirements:
- CPU: 2+ cores per node
- RAM: 2GB minimum, 4GB recommended
- Storage: 20GB+ with good I/O performance
- Network: Low-latency connections between nodes
Step-by-Step Tutorial
Step 1: Initial System Setup
First, update all three Ubuntu 24.04 systems and install essential packages:
# Run on all three nodes
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget gnupg2 software-properties-common python3-pip python3-venv
# Set hostnames for easier identification
# Node 1:
sudo hostnamectl set-hostname pg-node1
# Node 2:
sudo hostnamectl set-hostname pg-node2
# Node 3:
sudo hostnamectl set-hostname pg-node3
Step 2: Install PostgreSQL 16
Install PostgreSQL 16 from the official repository on all nodes:
# Add PostgreSQL official APT repository
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /usr/share/keyrings/postgresql.gpg
echo "deb [signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | sudo tee /etc/apt/sources.list.d/postgresql.list
# Install PostgreSQL 16
sudo apt update
sudo apt install -y postgresql-16 postgresql-16-dev postgresql-client-16
# Stop and disable default PostgreSQL service
sudo systemctl stop postgresql
sudo systemctl disable postgresql
Step 3: Deploy etcd Cluster
Install and configure etcd for distributed consensus across all nodes:
# Download and install etcd v3.5.10
ETCD_VERSION="3.5.10"
wget https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
tar -xzf etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
sudo mv etcd-v${ETCD_VERSION}-linux-amd64/etcd* /usr/local/bin/
sudo mkdir -p /var/lib/etcd
sudo useradd -r -s /bin/false etcd
sudo chown etcd:etcd /var/lib/etcd
Create etcd configuration file. Replace IP addresses with your actual node IPs:
# Node 1 (/etc/etcd/etcd.conf)
name: 'node1'
data-dir: '/var/lib/etcd'
initial-advertise-peer-urls: 'http://10.0.1.10:2380'
listen-peer-urls: 'http://10.0.1.10:2380'
advertise-client-urls: 'http://10.0.1.10:2379'
listen-client-urls: 'http://10.0.1.10:2379,http://127.0.0.1:2379'
initial-cluster: 'node1=http://10.0.1.10:2380,node2=http://10.0.1.11:2380,node3=http://10.0.1.12:2380'
initial-cluster-state: 'new'
initial-cluster-token: 'postgresql-cluster'
Step 4: Configure Patroni
Install Patroni for PostgreSQL high availability management:
# Create patroni user and install via pip
sudo useradd -r -s /bin/false patroni
sudo python3 -m pip install patroni[etcd] psycopg2-binary
# Create Patroni configuration directory
sudo mkdir -p /etc/patroni
sudo mkdir -p /var/lib/postgresql/16/main
sudo chown -R postgres:postgres /var/lib/postgresql
Create Patroni configuration file for each node:
# /etc/patroni/patroni.yml (Node 1 example)
scope: postgres-cluster
namespace: /db/
name: node1
restapi:
listen: 10.0.1.10:8008
connect_address: 10.0.1.10:8008
etcd:
hosts: 10.0.1.10:2379,10.0.1.11:2379,10.0.1.12:2379
bootstrap:
dcs:
ttl: 30
loop_wait: 10
retry_timeout: 30
maximum_lag_on_failover: 1048576
postgresql:
use_pg_rewind: true
use_slots: true
parameters:
wal_level: replica
hot_standby: "on"
max_connections: 100
max_wal_senders: 10
wal_keep_size: 128MB
hot_standby_feedback: "on"
ssl: "on"
ssl_cert_file: '/etc/ssl/certs/postgres.crt'
ssl_key_file: '/etc/ssl/private/postgres.key'
postgresql:
listen: 10.0.1.10:5432
connect_address: 10.0.1.10:5432
data_dir: /var/lib/postgresql/16/main
bin_dir: /usr/lib/postgresql/16/bin
pgpass: /tmp/pgpass
authentication:
replication:
username: replicator
password: "secure_repl_password"
superuser:
username: postgres
password: "secure_postgres_password"
tags:
nofailover: false
noloadbalance: false
clonefrom: false
nosync: false
Step 5: Generate TLS Certificates
Create self-signed certificates for secure PostgreSQL connections:
# Generate CA key and certificate
sudo openssl genrsa -out /etc/ssl/private/ca-key.pem 4096
sudo openssl req -new -x509 -key /etc/ssl/private/ca-key.pem -out /etc/ssl/certs/ca-cert.pem -days 365 -subj "/CN=PostgreSQL-CA"
# Generate server certificate for each node
sudo openssl genrsa -out /etc/ssl/private/postgres.key 2048
sudo openssl req -new -key /etc/ssl/private/postgres.key -out /tmp/postgres.csr -subj "/CN=postgres"
sudo openssl x509 -req -in /tmp/postgres.csr -CA /etc/ssl/certs/ca-cert.pem -CAkey /etc/ssl/private/ca-key.pem -out /etc/ssl/certs/postgres.crt -days 365 -CAcreateserial
# Set proper permissions
sudo chown postgres:postgres /etc/ssl/private/postgres.key
sudo chown postgres:postgres /etc/ssl/certs/postgres.crt
sudo chmod 600 /etc/ssl/private/postgres.key
sudo chmod 644 /etc/ssl/certs/postgres.crt
Step 6: Deploy HAProxy Load Balancer
Install and configure HAProxy for database connection routing:
# Install HAProxy
sudo apt install -y haproxy
# Configure HAProxy (/etc/haproxy/haproxy.cfg)
global
daemon
user haproxy
group haproxy
log stdout local0
defaults
mode tcp
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
log global
frontend postgres_frontend
bind *:5000
default_backend postgres_servers
frontend postgres_readonly_frontend
bind *:5001
default_backend postgres_readonly_servers
backend postgres_servers
balance leastconn
option tcp-check
tcp-check connect port 8008
tcp-check send GET\ /primary\ HTTP/1.0\r\n\r\n
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008 backup
server node3 10.0.1.12:5432 check port 8008 backup
backend postgres_readonly_servers
balance roundrobin
option tcp-check
tcp-check connect port 8008
tcp-check send GET\ /replica\?lag=1MB\ HTTP/1.0\r\n\r\n
server node1 10.0.1.10:5432 check port 8008
server node2 10.0.1.11:5432 check port 8008
server node3 10.0.1.12:5432 check port 8008
Step 7: Start Services and Initialize Cluster
Start all services in the proper order:
# Start etcd on all nodes
sudo systemctl start etcd
sudo systemctl enable etcd
# Start Patroni on all nodes (start node1 first)
sudo systemctl start patroni
sudo systemctl enable patroni
# Start HAProxy
sudo systemctl start haproxy
sudo systemctl enable haproxy
# Verify cluster status
patronictl -c /etc/patroni/patroni.yml list
Best Practices
Security Considerations:
- Use proper TLS certificates: Replace self-signed certificates with CA-signed ones for production
- Secure etcd communication: Enable TLS for etcd client-server and peer communications
- Network isolation: Use private networks and firewalls to restrict database access
- Strong passwords: Generate complex passwords for PostgreSQL users and store securely
Performance Optimization:
- Tune PostgreSQL parameters: Adjust
shared_buffers
,work_mem
, andmax_connections
based on workload - Monitor replication lag: Set up alerts for high replication lag between primary and replicas
- Use connection pooling: Implement PgBouncer for efficient connection management
- Regular maintenance: Schedule automated backups and consider automated backup solutions
Monitoring and Alerting:
- Monitor Patroni API endpoints for cluster health
- Set up alerts for failover events and replication issues
- Track database performance metrics and query execution times
Conclusion
You’ve successfully deployed a highly available PostgreSQL 16 cluster with automatic failover capabilities, load balancing, and TLS encryption. This production-ready setup ensures database continuity and optimal performance for your applications.
The combination of Patroni, etcd, and HAProxy provides robust failover mechanisms, distributed consensus, and intelligent traffic routing. Your cluster can now handle node failures gracefully while maintaining data consistency and application availability.
For enhanced performance and reliability, consider deploying this cluster on high-performance infrastructure. Onidel’s VPS solutions offer the compute power, network performance, and storage capabilities needed for demanding PostgreSQL workloads across multiple regions.