Granting privileges in PostgreSQL keeps application access predictable while limiting damage from mistakes, compromised credentials, or overly-permissive roles. A least-privilege role design also makes audits and incident response far easier than untangling blanket grants later.
PostgreSQL authorization is role-based and enforced at multiple layers: database-level access (such as CONNECT), schema-level access (such as USAGE), and object-level access for tables, sequences, and functions. The GRANT statement updates access control lists (ACLs) on those objects, and object ownership remains separate from granted privileges.
Default privileges matter for long-lived databases because GRANT … ON ALL TABLES only covers existing objects. ALTER DEFAULT PRIVILEGES applies only to future objects and only for objects created by the role specified (or the role executing the command), so the “owner/migration” role must be targeted correctly. Managed PostgreSQL services typically lack an OS-level postgres user, so the same SQL applies but the connection method differs.
Related: How to revoke privileges in PostgreSQL \\
Related: How to create roles in PostgreSQL
$ sudo -u postgres psql -d postgres -c "GRANT CONNECT ON DATABASE appdb TO appuser;" GRANT
$ sudo -u postgres psql -d appdb -c "GRANT USAGE ON SCHEMA public TO appuser;" GRANT
Granting CREATE on a schema allows object creation inside that schema, which is frequently broader than intended for a runtime role.
$ sudo -u postgres psql -d appdb -c "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO appuser;" GRANT
$ sudo -u postgres psql -d appdb -c "GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO appuser;" GRANT
USAGE enables nextval(), SELECT enables currval(), and UPDATE enables setval() on sequences.
$ sudo -u postgres psql -d appdb -c "GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO appuser;" GRANT
$ sudo -u postgres psql -d appdb -c "ALTER DEFAULT PRIVILEGES FOR ROLE appowner IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO appuser;" ALTER DEFAULT PRIVILEGES
The role in FOR ROLE must match the role that creates tables in the schema, which is commonly a migration/owner role rather than the runtime role.
$ sudo -u postgres psql -d appdb -c "ALTER DEFAULT PRIVILEGES FOR ROLE appowner IN SCHEMA public GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO appuser;" ALTER DEFAULT PRIVILEGES
$ sudo -u postgres psql -d appdb -c "ALTER DEFAULT PRIVILEGES FOR ROLE appowner IN SCHEMA public GRANT EXECUTE ON FUNCTIONS TO appuser;" ALTER DEFAULT PRIVILEGES
$ sudo -u postgres psql -d appdb -c "\dp"
Access privileges
Schema | Name | Type | Access privileges | Column privileges | Policies
--------+-------------------------+-------------------+---------------------------+-------------------+----------
public | events | partitioned table | postgres=arwdDxt/postgres+| |
| | | appuser=arwd/postgres | |
public | orders | table | appowner=arwdDxt/appowner+| |
| | | appuser=arwd/appowner | |
public | orders_id_seq | sequence | appowner=rwU/appowner +| |
| | | appuser=rwU/appowner | |
##### snipped #####
$ sudo -u postgres psql -d appdb -c "\ddp"
Default access privileges
Owner | Schema | Type | Access privileges
----------+--------+----------+-----------------------
appowner | public | function | appuser=X/appowner
appowner | public | sequence | appuser=rwU/appowner
appowner | public | table | appuser=arwd/appowner