Installation & Configuration
Installing PostgreSQL
Linux (Debian/Ubuntu)
The PostgreSQL Global Development Group maintains official APT repositories with the latest versions:
sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh
sudo apt install -y postgresql-16
This installs the server, client tools, and creates a postgres system user. The cluster initializes automatically and starts on port 5432.
Linux (RHEL/Fedora)
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf install -y postgresql16-server
sudo /usr/pgsql-16/bin/postgresql-16-setup initdb
sudo systemctl enable --now postgresql-16
On RHEL-based systems, you must explicitly initialize the cluster and start the service.
macOS
Homebrew is the simplest path:
brew install postgresql@16
brew services start postgresql@16
Postgres.app is an alternative that provides a menu bar icon and bundles PostGIS and other extensions. It is convenient for development but not suitable for production.
Docker
docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secretpassword \
-p 5432:5432 \
-v pgdata:/var/lib/postgresql/data \
postgres:16
The -v pgdata flag creates a named volume so data persists across container restarts. Without it, stopping the container destroys your data.
Connect from the host:
psql -h localhost -U postgres
The Data Directory
Every Postgres cluster stores its data in a single directory, commonly called PGDATA. On Debian/Ubuntu, this is typically /var/lib/postgresql/16/main. On RHEL, it is /var/lib/pgsql/16/data.
Key contents:
PGDATA/
postgresql.conf # Main configuration
pg_hba.conf # Client authentication
pg_ident.conf # User name mapping
base/ # Per-database subdirectories
global/ # Cluster-wide tables
pg_wal/ # Write-ahead log segments
pg_xact/ # Transaction commit status
postmaster.pid # PID file for the running server
Never edit files inside base/ or pg_wal/ manually. Corruption is the guaranteed outcome.
postgresql.conf: The Settings That Matter
Postgres ships with conservative defaults designed to run on minimal hardware. A production server needs tuning. These are the settings that matter most.
shared_buffers
The amount of memory Postgres uses for caching data pages. The default (128MB) is absurdly low for any real workload.
# Set to 25% of total RAM as a starting point
shared_buffers = 4GB # on a 16GB server
Going above 25% of RAM rarely helps because the OS page cache handles the rest. On machines with 256GB+ RAM, 8-16GB is often enough.
work_mem
Memory allocated per sort or hash operation, per query. This is not a global pool. A complex query with five hash joins uses 5x this value.
# Start conservative, increase for analytical workloads
work_mem = 64MB
Setting this too high on an OLTP system with hundreds of connections will exhaust memory. Calculate: max_connections * work_mem * average_sorts_per_query should fit in available RAM.
effective_cache_size
Tells the planner how much memory is available for caching (shared_buffers + OS cache). This does not allocate memory. It only affects query planning decisions.
# Set to 50-75% of total RAM
effective_cache_size = 12GB # on a 16GB server
Setting this too low makes the planner favor sequential scans over index scans.
max_connections
The maximum number of concurrent connections. Each connection consumes roughly 5-10MB of RAM and runs in its own OS process.
# Most applications need far fewer than the default 100
max_connections = 200
If you think you need 500+ connections, you need a connection pooler (PgBouncer), not more connections. Postgres performance degrades with too many active backends competing for CPU and locks.
maintenance_work_mem
Memory for maintenance operations: VACUUM, CREATE INDEX, ALTER TABLE ADD FOREIGN KEY.
# Can be much larger than work_mem
maintenance_work_mem = 1GB
These operations run infrequently, so a generous allocation speeds up index builds and vacuum runs.
WAL-Related Settings
wal_buffers = 64MB
checkpoint_completion_target = 0.9
max_wal_size = 4GB
min_wal_size = 1GB
These control how WAL data is written and how frequently checkpoints occur. The defaults are reasonable for small systems but too conservative for write-heavy workloads.
Logging
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%Y-%m-%d.log'
log_min_duration_statement = 1000 # log queries slower than 1 second
log_line_prefix = '%t [%p]: user=%u,db=%d '
log_checkpoints = on
log_lock_waits = on
log_temp_files = 0 # log all temp file usage
Slow query logging is one of the most valuable production settings. Start with 1000ms and lower it as you optimize.
Applying Configuration Changes
Most settings take effect after a reload (no restart required):
sudo systemctl reload postgresql
Or from within psql:
SELECT pg_reload_conf();
Some settings (shared_buffers, max_connections, wal_buffers) require a full restart:
sudo systemctl restart postgresql
Check which settings need a restart:
SELECT name, setting, context
FROM pg_settings
WHERE context = 'postmaster';
pg_hba.conf: Client Authentication
The pg_hba.conf file controls who can connect, from where, and how they authenticate. Each line is a rule, evaluated top to bottom. First match wins.
# 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
host myapp myapp 10.0.0.0/8 scram-sha-256
host all all 0.0.0.0/0 reject
Common authentication methods:
- peer: OS user must match database user. Only for local (Unix socket) connections.
- scram-sha-256: Password-based, cryptographically strong. Use this for network connections.
- md5: Legacy password method. Weaker than scram-sha-256. Migrate away from it.
- cert: TLS client certificate authentication. Strongest option for production.
- reject: Explicitly deny the connection.
After editing pg_hba.conf, reload:
sudo systemctl reload postgresql
A common mistake is putting a broad trust or reject rule above more specific rules. Rules are evaluated in order. Put specific rules first.
Starting, Stopping & Reloading
systemd (Most Linux Systems)
sudo systemctl start postgresql
sudo systemctl stop postgresql
sudo systemctl restart postgresql
sudo systemctl reload postgresql
sudo systemctl status postgresql
pg_ctl (Direct Control)
pg_ctl -D /var/lib/postgresql/16/main start
pg_ctl -D /var/lib/postgresql/16/main stop -m fast
pg_ctl -D /var/lib/postgresql/16/main reload
pg_ctl -D /var/lib/postgresql/16/main status
Stop modes:
- smart: Wait for all clients to disconnect. Can hang indefinitely.
- fast: Roll back active transactions and disconnect clients. The normal way to stop.
- immediate: Abort all processes. Requires crash recovery on next start. Emergency only.
macOS (Homebrew)
brew services start postgresql@16
brew services stop postgresql@16
brew services restart postgresql@16
psql Basics
psql is the standard command-line client. It is the tool every Postgres user should learn first.
Connecting
psql -h localhost -p 5432 -U myuser -d mydb
Or with a connection string:
psql "postgresql://myuser:mypassword@localhost:5432/mydb?sslmode=require"
Essential Commands
\l -- list databases
\c mydb -- connect to a different database
\dt -- list tables in current schema
\d users -- describe a table (columns, indexes, constraints)
\di -- list indexes
\du -- list roles/users
\df -- list functions
\q -- quit
Running SQL
SELECT version();
version
------------------------------------------------------------
PostgreSQL 16.2 on x86_64-pc-linux-gnu, compiled by ...
Creating a Database & User
CREATE USER myapp WITH PASSWORD 'strong_password_here';
CREATE DATABASE myappdb OWNER myapp;
GRANT ALL PRIVILEGES ON DATABASE myappdb TO myapp;
Common Pitfalls
- Running with default shared_buffers (128MB). This is never appropriate for production. Even a small server should allocate at least 1GB.
- Setting max_connections to 1000. Each connection is a process. Use PgBouncer for connection pooling instead.
- Using trust authentication in pg_hba.conf. Trust means no password required. Acceptable for local development, dangerous in production.
- Editing the wrong postgresql.conf. On Debian/Ubuntu, configuration lives in
/etc/postgresql/16/main/, not in the data directory. CheckSHOW config_file;to find the active file. - Forgetting to reload after configuration changes. Changes to postgresql.conf and pg_hba.conf do not take effect until you reload or restart.
- Not setting up logging from the start. Slow query logging catches performance problems early. Enable it on day one.
Key Takeaways
- Install from official repositories to get the latest version and clean upgrade paths.
- The four most impactful settings are shared_buffers, work_mem, effective_cache_size, and max_connections. Tune these first.
- pg_hba.conf controls authentication. Rules are evaluated top to bottom. Use scram-sha-256 for network connections.
- Most configuration changes only require a reload, not a restart. Check the
contextcolumn in pg_settings. - psql is the essential client tool. Learn the backslash commands before reaching for a GUI.
- Always set up slow query logging in production. It is the single most useful diagnostic tool.