Identity
- Unit:
postgresql@16-main.service(Debian/Ubuntu),postgresql-16.service(RHEL/Fedora) - Config (Debian):
/etc/postgresql/16/main/postgresql.conf,/etc/postgresql/16/main/pg_hba.conf - Config (RHEL):
/var/lib/pgsql/16/data/postgresql.conf,/var/lib/pgsql/16/data/pg_hba.conf - Data dir (Debian):
/var/lib/postgresql/16/main/ - Data dir (RHEL):
/var/lib/pgsql/16/data/ - Logs (Debian):
/var/log/postgresql/postgresql-16-main.log - Logs (RHEL):
journalctl -u postgresql-16, or$PGDATA/log/if logging_collector=on - Postgres superuser:
postgres(OS user and DB superuser — separate concepts) - Install (Debian):
apt install postgresql-16 - Install (RHEL):
dnf install postgresql16-server && postgresql-16-setup initdb
Key Operations
| Operation | Command |
|---|---|
| Service status | systemctl status postgresql@16-main (Debian) / systemctl status postgresql-16 (RHEL) |
| Reload config | sudo systemctl reload postgresql@16-main or SELECT pg_reload_conf(); in psql |
| Connect as postgres | sudo -u postgres psql |
| Connect to specific db | sudo -u postgres psql -d mydb or psql -h 127.0.0.1 -U myuser -d mydb |
| List databases | \l or SELECT datname FROM pg_database; |
| List users/roles | \du or SELECT rolname, rolsuper FROM pg_roles; |
| List tables | \dt (current schema), \dt *.* (all schemas) |
| Create database | CREATE DATABASE mydb; |
| Create user | CREATE USER myuser WITH PASSWORD 'secret'; |
| Grant all on database | GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser; |
| Grant schema usage | GRANT USAGE ON SCHEMA public TO myuser; GRANT ALL ON ALL TABLES IN SCHEMA public TO myuser; |
| Dump single database | pg_dump -U postgres -F c -f mydb.dump mydb |
| Dump all databases | pg_dumpall -U postgres -f all_dbs.sql |
| Restore custom-format dump | pg_restore -U postgres -d mydb -F c mydb.dump |
| Restore SQL dump | psql -U postgres -d mydb -f mydb.sql |
| Check active connections | SELECT pid, usename, application_name, state, query FROM pg_stat_activity; |
| Kill a connection | SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid = <pid>; |
| Vacuum a table | VACUUM VERBOSE ANALYZE mytable; |
| Full vacuum (locks table) | VACUUM FULL mytable; (use sparingly — acquires exclusive lock) |
| Check table sizes | SELECT relname, pg_size_pretty(pg_total_relation_size(oid)) FROM pg_class WHERE relkind='r' ORDER BY pg_total_relation_size(oid) DESC LIMIT 20; |
| Replication status | SELECT * FROM pg_stat_replication; (primary) / SELECT * FROM pg_stat_wal_receiver; (replica) |
Expected Ports
- 5432/tcp (PostgreSQL default)
- Verify:
ss -tlnp | grep postgres - Firewall (ufw):
sudo ufw allow from <client_ip> to any port 5432 - Firewall (firewalld):
sudo firewall-cmd --add-service=postgresql --permanent && sudo firewall-cmd --reload
Health Checks
systemctl is-active postgresql@16-main→activesudo -u postgres pg_isready→/var/run/postgresql:5432 - accepting connectionssudo -u postgres psql -c "SELECT version();"→ version string, no errorssudo -u postgres psql -c "SELECT count(*) FROM pg_stat_activity;"→ integer row count
Common Failures
| Symptom | Likely cause | Check / Fix |
|---|---|---|
password authentication failed for user "foo" |
Wrong password, or pg_hba.conf requires a different method | Check pg_hba.conf auth method for the source host; reset password with ALTER USER foo PASSWORD 'newpass'; |
no pg_hba.conf entry for host "x.x.x.x" |
Client IP not listed in pg_hba.conf | Add a host line for the IP range; reload with SELECT pg_reload_conf(); |
connection refused on 5432 |
PostgreSQL not listening on that interface | Check listen_addresses in postgresql.conf; default is localhost — change to '*' for all interfaces |
FATAL: remaining connection slots are reserved |
max_connections reached; only superuser slots remain |
Check SELECT count(*) FROM pg_stat_activity;; add a connection pooler (PgBouncer) or raise max_connections |
| Lock contention / queries waiting | Row or table lock held by a blocked or idle transaction | SELECT * FROM pg_locks JOIN pg_stat_activity USING (pid) WHERE granted = false; — identify and terminate the blocker |
| Autovacuum not keeping up | Table bloat growing; n_dead_tup high |
SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;; tune autovacuum_vacuum_cost_delay |
| Disk full (WAL accumulation) | WAL not being archived or replicas lagging | SELECT pg_walfile_name(pg_current_wal_lsn());; check pg_stat_replication lag; free space or resolve replica lag |
pg_dump: error: query was canceled |
Statement timeout or pg_dump lacking privileges | Run as postgres superuser; check statement_timeout setting |
Pain Points
- pg_hba.conf: first match wins — rules are evaluated top-to-bottom and the first matching line applies. A broad rule placed above a restrictive one will silently override it. Ordering matters; add specific rules before general ones.
listen_addressesdefaults tolocalhost— PostgreSQL refuses remote TCP connections out of the box. To allow remote access, setlisten_addresses = '*'(or a specific IP) in postgresql.conf and add a matchinghostrule in pg_hba.conf, then reload both.- OS user
postgresvs database rolepostgres— they share a name but are separate. The OS user gets apeerauth entry in pg_hba.conf, which bypasses password checks. Application connections use a password-authenticated database role — do not conflate the two. - Connection pooling is not built in — PostgreSQL creates one OS process per connection. At ~100–200 connections, context-switching overhead becomes measurable. PgBouncer in transaction mode is the standard solution; plan for it before hitting production.
- WAL archiving complexity — enabling
archive_moderequires settingarchive_commandto a working script before enabling; a failing archive command will eventually stall the server oncemax_wal_sizeis consumed. - Major version upgrades require explicit migration —
pg_upgrademust be used (not a package upgrade). Data directory format changes between major versions (e.g., 15 → 16). Plan and test upgrades; logical replication is an alternative for low-downtime upgrades. work_memis per sort/hash operation, not per query — a single complex query can allocatework_memmany times in parallel. Setting it too high with many concurrent queries causes OOM. Start at 4–16MB globally and override per-session for known heavy queries.- Replica promotion is permanent — once a standby is promoted with
pg_ctl promoteorSELECT pg_promote(), it becomes a primary and cannot rejoin the old primary without a full re-sync (pg_basebackup).
References
See references/ for:
postgresql.conf.annotated— full config with every directive explained, plus an annotated pg_hba.conf examplecommon-patterns.md— initial setup, remote access, backup/restore, replication, query analysis, and PgBouncerdocs.md— official documentation links