Roles and permissions
In Postgres, users and groups are both roles.
CREATE ROLE app_user WITH LOGIN PASSWORD 'strong-password';
CREATE ROLE readonly;
GRANT CONNECT ON DATABASE bookstore TO app_user;
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO app_user;
-- apply to tables created later too
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;Least privilege
Your application should not connect as postgres (the superuser).
Create a dedicated role with only the permissions it needs. A SQL
injection in an app connected as superuser can do anything; the same bug
on a restricted role usually cannot.
A read-only reporting role:
CREATE ROLE reporting WITH LOGIN PASSWORD '...';
GRANT CONNECT ON DATABASE bookstore TO reporting;
GRANT USAGE ON SCHEMA public TO reporting;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO reporting;Row-level security
Restrict which rows a role can see — the foundation of multi-tenant applications:
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY own_orders ON orders
FOR SELECT
USING (user_id = current_setting('app.user_id')::BIGINT);The application sets SET app.user_id = '42' per request, and the
database enforces the boundary even if a query forgets its WHERE.
Backups
# single database, compressed custom format
pg_dump -U postgres -Fc bookstore > bookstore.dump
# plain SQL
pg_dump -U postgres bookstore > bookstore.sql
# everything, including roles
pg_dumpall -U postgres > all.sqlRestoring:
pg_restore -U postgres -d bookstore bookstore.dump
psql -U postgres -d bookstore -f bookstore.sqlAn untested backup is not a backup. Restore it into a scratch database on a schedule and confirm the data is really there.
Monitoring
-- currently running queries
SELECT pid, now() - query_start AS duration, state, query
FROM pg_stat_activity
WHERE state <> 'idle'
ORDER BY duration DESC;
-- cancel / kill
SELECT pg_cancel_backend(pid);
SELECT pg_terminate_backend(pid);
-- table and index sizes
SELECT relname, pg_size_pretty(pg_total_relation_size(relid))
FROM pg_catalog.pg_statio_user_tables
ORDER BY pg_total_relation_size(relid) DESC;Maintenance
Postgres uses MVCC — updates leave behind dead row versions that
autovacuum cleans up. It is on by default; the things to watch are
long-running transactions (which block cleanup) and tables with very high
update rates.
VACUUM ANALYZE books;
SELECT relname, n_dead_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;Security checklist
- App connects as a limited role, never superuser.
- Passwords via environment variables, never in the repository.
scram-sha-256authentication inpg_hba.conf.- TLS for any connection crossing a network.
- Postgres not exposed to the public internet.
- Parameterised queries in the application — always.