Learn to install PostgreSQL 17 from official repositories, optimize performance with proper memory settings, and implement security best practices including SSL encryption and authentication hardening.
Prerequisites
- Root or sudo access
- At least 4GB RAM recommended
- Basic understanding of SQL and database concepts
What this solves
PostgreSQL 17 brings significant performance improvements and new features for production databases. This tutorial helps you install PostgreSQL 17 from official repositories, configure optimal performance settings for your workload, and implement security hardening to protect your data.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest security patches.
sudo apt update && sudo apt upgrade -y
Add PostgreSQL official repository
Install PostgreSQL 17 from the official PostgreSQL repository to get the latest version and timely security updates.
sudo apt install -y wget ca-certificates
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
Install PostgreSQL 17
Install the PostgreSQL 17 server, client tools, and contrib modules for additional functionality.
sudo apt install -y postgresql-17 postgresql-client-17 postgresql-contrib-17
Initialize PostgreSQL database
Initialize the database cluster and start the PostgreSQL service. On RHEL-based systems, you must manually initialize the database.
sudo systemctl start postgresql
sudo systemctl enable postgresql
Set PostgreSQL superuser password
Set a strong password for the postgres superuser account to secure your database installation.
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'YourSecurePassword123!';"
sudo -u postgres psql -c "\q"
Performance tuning configuration
Calculate optimal shared_buffers
Configure shared_buffers to 25% of total RAM for optimal caching performance. First, check your available memory.
free -h
echo "For 8GB RAM, set shared_buffers = 2GB"
echo "For 16GB RAM, set shared_buffers = 4GB"
Configure performance settings
Edit the PostgreSQL configuration file to optimize memory usage, checkpoints, and query performance.
sudo nano /etc/postgresql/17/main/postgresql.conf
Add these optimized settings at the end of the file:
# Memory Configuration
shared_buffers = 2GB # 25% of total RAM
effective_cache_size = 6GB # 75% of total RAM
work_mem = 32MB # Per query operation
maintenance_work_mem = 512MB # For VACUUM, CREATE INDEX
Checkpoint Configuration
checkpoint_completion_target = 0.9
wal_buffers = 64MB
max_wal_size = 4GB
min_wal_size = 1GB
Query Planner
random_page_cost = 1.1 # For SSD storage
effective_io_concurrency = 200 # For SSD storage
Connection Settings
max_connections = 200
Logging
log_destination = 'stderr'
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_min_duration_statement = 1000 # Log slow queries
Configure connection limits
Set up connection pooling configuration to handle concurrent connections efficiently.
# Additional connection settings
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.max = 10000
pg_stat_statements.track = all
Security hardening configuration
Configure authentication methods
Edit pg_hba.conf to enforce secure authentication methods and restrict access by IP address.
sudo cp /etc/postgresql/17/main/pg_hba.conf /etc/postgresql/17/main/pg_hba.conf.backup
sudo nano /etc/postgresql/17/main/pg_hba.conf
Replace the default configuration with these secure settings:
# TYPE DATABASE USER ADDRESS METHOD
Local connections require password
local all postgres scram-sha-256
local all all scram-sha-256
IPv4 local connections require password
host all all 127.0.0.1/32 scram-sha-256
host all all 10.0.0.0/8 scram-sha-256
host all all 192.168.0.0/16 scram-sha-256
IPv6 local connections require password
host all all ::1/128 scram-sha-256
Reject all other connections
host all all 0.0.0.0/0 reject
Enable SSL encryption
Generate SSL certificates and configure PostgreSQL to use encrypted connections.
sudo -u postgres openssl req -new -x509 -days 365 -nodes -text -out /etc/postgresql/17/main/server.crt -keyout /etc/postgresql/17/main/server.key -subj "/CN=postgresql.example.com"
sudo chown postgres:postgres /etc/postgresql/17/main/server.crt /etc/postgresql/17/main/server.key
sudo chmod 600 /etc/postgresql/17/main/server.key
Enable SSL in PostgreSQL configuration:
# SSL Configuration
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_protocols = 'TLSv1.2,TLSv1.3'
ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'
ssl_prefer_server_ciphers = on
Configure firewall rules
Set up firewall rules to allow PostgreSQL connections only from trusted networks.
sudo ufw allow from 192.168.1.0/24 to any port 5432
sudo ufw allow from 10.0.0.0/8 to any port 5432
sudo ufw enable
Create application database and user
Create a dedicated database and user with minimal privileges for your applications.
sudo -u postgres createdb appdb
sudo -u postgres psql -c "CREATE USER appuser WITH ENCRYPTED PASSWORD 'SecureAppPassword123!';"
sudo -u postgres psql -c "GRANT CONNECT ON DATABASE appdb TO appuser;"
sudo -u postgres psql -c "\c appdb"
sudo -u postgres psql -d appdb -c "GRANT USAGE ON SCHEMA public TO appuser;"
sudo -u postgres psql -d appdb -c "GRANT CREATE ON SCHEMA public TO appuser;"
Restart PostgreSQL service
Apply all configuration changes by restarting the PostgreSQL service.
sudo systemctl restart postgresql
sudo systemctl status postgresql
Backup automation setup
Create backup directory and script
Set up automated backups using pg_dump with compression and rotation.
sudo mkdir -p /var/backups/postgresql
sudo chown postgres:postgres /var/backups/postgresql
sudo chmod 750 /var/backups/postgresql
Create the backup script:
#!/bin/bash
PostgreSQL backup script
BACKUP_DIR="/var/backups/postgresql"
DATE=$(date +"%Y%m%d_%H%M%S")
DATABASE="appdb"
BACKUP_FILE="${BACKUP_DIR}/${DATABASE}_${DATE}.sql.gz"
Create backup
pg_dump -h localhost -U postgres -d $DATABASE | gzip > $BACKUP_FILE
Set ownership and permissions
chown postgres:postgres $BACKUP_FILE
chmod 640 $BACKUP_FILE
Remove backups older than 7 days
find $BACKUP_DIR -name "*.sql.gz" -mtime +7 -delete
Log backup completion
echo "$(date): Backup completed: $BACKUP_FILE" >> /var/log/pg_backup.log
sudo chmod +x /usr/local/bin/pg_backup.sh
sudo chown postgres:postgres /usr/local/bin/pg_backup.sh
Schedule automated backups
Add a cron job to run backups daily at 2 AM.
sudo -u postgres crontab -e
Add this line to the crontab:
0 2 * /usr/local/bin/pg_backup.sh
Verify your setup
Test your PostgreSQL installation, performance settings, and security configuration.
# Check PostgreSQL version
sudo -u postgres psql -c "SELECT version();"
Test SSL connection
psql "host=localhost dbname=appdb user=appuser sslmode=require" -c "SELECT current_database();"
Check current settings
sudo -u postgres psql -c "SHOW shared_buffers;"
sudo -u postgres psql -c "SHOW max_connections;"
sudo -u postgres psql -c "SHOW ssl;"
Test backup script
sudo -u postgres /usr/local/bin/pg_backup.sh
ls -la /var/backups/postgresql/
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Connection refused on port 5432 | PostgreSQL not listening on correct interface | Set listen_addresses = '*' in postgresql.conf |
| Password authentication failed | Incorrect pg_hba.conf configuration | Check authentication method and user permissions in pg_hba.conf |
| SSL connection error | Certificate permissions or missing SSL config | Ensure server.key has 600 permissions and owned by postgres user |
| High memory usage | shared_buffers set too high | Reduce shared_buffers to 25% of total RAM |
| Slow query performance | Inadequate work_mem or missing indexes | Increase work_mem and analyze slow query log |
| Backup script fails | Permission issues or missing pg_dump | Ensure postgres user can write to backup directory |
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'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Global variables
POSTGRES_PASSWORD=""
RAM_GB=""
SHARED_BUFFERS=""
EFFECTIVE_CACHE_SIZE=""
# Usage function
usage() {
echo "Usage: $0 [postgres_password]"
echo " postgres_password: Password for PostgreSQL superuser (optional, will prompt if not provided)"
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function for rollback
cleanup() {
log_error "Installation failed. Performing cleanup..."
systemctl stop postgresql-17 2>/dev/null || systemctl stop postgresql 2>/dev/null || true
if [[ "$PKG_MGR" == "apt" ]]; then
apt-get remove --purge -y postgresql-17* 2>/dev/null || true
rm -f /etc/apt/sources.list.d/pgdg.list /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg
else
dnf remove -y postgresql17* 2>/dev/null || yum remove -y postgresql17* 2>/dev/null || true
fi
}
# Set trap for cleanup on error
trap cleanup ERR
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Parse arguments
if [[ $# -gt 1 ]]; then
usage
elif [[ $# -eq 1 ]]; then
POSTGRES_PASSWORD="$1"
fi
# Auto-detect distribution
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"
PG_VERSION="17"
PG_CONFIG_DIR="/etc/postgresql/17/main"
PG_SERVICE="postgresql"
;;
almalinux|rocky|centos|rhel|ol)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PG_VERSION="17"
PG_CONFIG_DIR="/var/lib/pgsql/17/data"
PG_SERVICE="postgresql-17"
;;
fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
PG_VERSION="17"
PG_CONFIG_DIR="/var/lib/pgsql/17/data"
PG_SERVICE="postgresql-17"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
PG_VERSION="17"
PG_CONFIG_DIR="/var/lib/pgsql/17/data"
PG_SERVICE="postgresql-17"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "Detected distribution: $PRETTY_NAME"
log_info "Package manager: $PKG_MGR"
# Prompt for password if not provided
if [[ -z "$POSTGRES_PASSWORD" ]]; then
echo -n "Enter PostgreSQL superuser password: "
read -s POSTGRES_PASSWORD
echo
if [[ -z "$POSTGRES_PASSWORD" ]]; then
log_error "Password cannot be empty"
exit 1
fi
fi
# Calculate memory settings
RAM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
RAM_GB=$((RAM_KB / 1024 / 1024))
SHARED_BUFFERS="${RAM_GB}GB"
EFFECTIVE_CACHE_SIZE="$((RAM_GB * 3))GB"
if [[ $RAM_GB -lt 4 ]]; then
SHARED_BUFFERS="1GB"
EFFECTIVE_CACHE_SIZE="2GB"
fi
echo "[1/8] Updating system packages..."
$PKG_UPDATE
echo "[2/8] Adding PostgreSQL official repository..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL wget ca-certificates gnupg
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg
chmod 644 /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg
echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
chmod 644 /etc/apt/sources.list.d/pgdg.list
apt update
else
if command -v dnf &> /dev/null; then
$PKG_INSTALL https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
else
$PKG_INSTALL https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
fi
fi
echo "[3/8] Installing PostgreSQL 17..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL postgresql-17 postgresql-client-17 postgresql-contrib-17
else
$PKG_INSTALL postgresql17-server postgresql17 postgresql17-contrib
fi
echo "[4/8] Initializing PostgreSQL database..."
if [[ "$PKG_MGR" == "apt" ]]; then
systemctl start $PG_SERVICE
systemctl enable $PG_SERVICE
else
/usr/pgsql-17/bin/postgresql-17-setup initdb
systemctl enable --now $PG_SERVICE
fi
echo "[5/8] Setting PostgreSQL superuser password..."
sleep 2
sudo -u postgres psql -c "ALTER USER postgres PASSWORD '$POSTGRES_PASSWORD';"
echo "[6/8] Configuring performance settings..."
PG_CONF="$PG_CONFIG_DIR/postgresql.conf"
cp "$PG_CONF" "$PG_CONF.backup"
cat >> "$PG_CONF" << EOF
# Performance Configuration Added by Install Script
shared_buffers = $SHARED_BUFFERS
effective_cache_size = $EFFECTIVE_CACHE_SIZE
work_mem = 32MB
maintenance_work_mem = 512MB
# Checkpoint Configuration
checkpoint_completion_target = 0.9
wal_buffers = 64MB
max_wal_size = 4GB
min_wal_size = 1GB
# Query Planner
random_page_cost = 1.1
effective_io_concurrency = 200
# Connection Settings
max_connections = 200
# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_min_duration_statement = 1000
# Extensions
shared_preload_libraries = 'pg_stat_statements'
EOF
echo "[7/8] Configuring security hardening..."
PG_HBA="$PG_CONFIG_DIR/pg_hba.conf"
cp "$PG_HBA" "$PG_HBA.backup"
cat > "$PG_HBA" << EOF
# PostgreSQL Client Authentication Configuration File
# TYPE DATABASE USER ADDRESS METHOD
# Local connections
local all postgres peer
local all all md5
# IPv4 local connections
host all all 127.0.0.1/32 md5
# IPv6 local connections
host all all ::1/128 md5
EOF
chmod 640 "$PG_HBA"
if [[ "$PKG_MGR" == "apt" ]]; then
chown postgres:postgres "$PG_HBA"
else
chown postgres:postgres "$PG_HBA"
fi
# Configure firewall
if command -v ufw &> /dev/null; then
ufw --force enable
ufw allow 22/tcp
elif command -v firewall-cmd &> /dev/null; then
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --reload
fi
echo "[8/8] Restarting PostgreSQL and verifying installation..."
systemctl restart $PG_SERVICE
# Verification checks
sleep 3
if systemctl is-active --quiet $PG_SERVICE; then
log_success "PostgreSQL service is running"
else
log_error "PostgreSQL service is not running"
exit 1
fi
# Test database connection
if sudo -u postgres psql -c "SELECT version();" &>/dev/null; then
log_success "Database connection test passed"
else
log_error "Database connection test failed"
exit 1
fi
# Display final information
log_success "PostgreSQL 17 installation completed successfully!"
echo
log_info "Configuration details:"
echo " - PostgreSQL version: $(sudo -u postgres psql -t -c 'SELECT version();' | head -1 | xargs)"
echo " - Service name: $PG_SERVICE"
echo " - Config directory: $PG_CONFIG_DIR"
echo " - RAM detected: ${RAM_GB}GB"
echo " - Shared buffers: $SHARED_BUFFERS"
echo " - Effective cache size: $EFFECTIVE_CACHE_SIZE"
echo
log_info "Next steps:"
echo " 1. Create application databases and users"
echo " 2. Configure network access in pg_hba.conf if needed"
echo " 3. Set up regular backups"
echo " 4. Monitor performance and adjust settings as needed"
echo
log_warning "Backup files created:"
echo " - $PG_CONF.backup"
echo " - $PG_HBA.backup"
Review the script before running. Execute with: bash install.sh