Connections and security
Roles and privileges
Every user and group in PostgreSQL is a role. A query is allowed only if the role, directly or through roles it belongs to, holds the right privileges on each object it touches, and row-level security may then hide rows.
In plain words
PostgreSQL has one concept for both users and groups: the role. A role that can log in is what we call a user; a role that others are members of works as a group. Members normally inherit the group's privileges.
Every object (database, schema, table, function…) has an owner. Ownership carries powerful object-management rights, while other roles need privileges handed out with GRANT and removed with REVOKE. Some operations still require superuser or another special role/privilege. To read a table through normal name resolution you typically need database CONNECT, schema USAGE, and table SELECT.
On top of object privileges, row-level security (RLS) policies can restrict which rows a role may read or modify. Policy expressions act like additional row-security qualifications, with important owner, BYPASSRLS and superuser rules. Pick a user below and follow the checks.
For DBAs and operators
Roles and membership
CREATE ROLE analyst NOLOGIN;
CREATE ROLE alice LOGIN PASSWORD '…';
GRANT analyst TO alice; -- INHERIT TRUE by default
GRANT analyst TO dave WITH INHERIT FALSE; -- must SET ROLE analystSince PostgreSQL 16 membership has per-grant options INHERIT, SET and ADMIN, and CREATEROLE no longer lets a role manage roles it was not granted ADMIN on. Superusers (and the bootstrap role) skip all privilege checks, including RLS; BYPASSRLS skips only RLS.
Privileges
Table: SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, MAINTAIN (MAINTAIN is new in PostgreSQL 17: VACUUM, ANALYZE, REINDEX, CLUSTER, REFRESH MATERIALIZED VIEW, LOCK). Schema: USAGE, CREATE. Database: CONNECT, CREATE, TEMP. \dp shows ACLs, e.g. postgres=arwdDxtm/postgres (letters for each privilege, m = MAINTAIN) and analyst=r/postgres.
Defaults that surprise people
PUBLIC(everyone) getsCONNECTandTEMPon new databases andEXECUTEon new functions.- Since PostgreSQL 15
PUBLICno longer hasCREATEon thepublicschema, and the schema is owned bypg_database_owner. Apps that silently created tables there now fail withERROR: permission denied for schema public. GRANT … ON ALL TABLES IN SCHEMAonly covers existing tables. For future ones useALTER DEFAULT PRIVILEGES [FOR ROLE creator] IN SCHEMA sales GRANT SELECT ON TABLES TO analyst; it applies to objects created by that creator role only.- Only the owner (or a member of the owning role) can
ALTER/DROP:ERROR: must be owner of table orders. Use a group role as owner in shared environments.
Predefined roles
pg_read_all_data / pg_write_all_data (14+), pg_monitor, pg_read_all_stats, pg_read_all_settings, pg_signal_backend, pg_checkpoint (15+), pg_use_reserved_connections and pg_create_subscription (16+), pg_maintain (17+: MAINTAIN on all relations), pg_signal_autovacuum_worker (18), pg_read_server_files, pg_write_server_files, pg_execute_server_program. Granting these is much safer than handing out superuser.
Row-level security
ALTER TABLE sales.orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY own_rows ON sales.orders
USING (sales_rep = current_user);With RLS enabled and no matching policy, a role sees no rows (no error). Owners are exempt unless FORCE ROW LEVEL SECURITY. pg_read_all_data does not bypass RLS. Inserting a row the policy would hide fails with ERROR: new row violates row-level security policy for table "orders". pg_dump sets row_security = off and errors out rather than dump partial data.
SELECT r.rolname, m.rolname AS member_of, am.inherit_option
FROM pg_auth_members am
JOIN pg_roles r ON r.oid = am.member
JOIN pg_roles m ON m.oid = am.roleid;
SELECT has_table_privilege('alice', 'sales.orders', 'SELECT');For developers: inside the source
Where each check happens
login InitializeSessionUserId() utils/init/miscinit.c rolcanlogin
CheckMyDatabase() utils/init/postinit.c ACL_CONNECT
parse RangeVarGetRelidExtended()
LookupExplicitNamespace() catalog/namespace.c ACL_USAGE
rewrite fireRIRrules()
get_row_security_policies() rewrite/rowsecurity.c
execute InitPlan() → ExecCheckPermissions() executor/execMain.c
ExecCheckOneRelPerms() → pg_class_aclmask()
pg_class_aclmask_ext() → aclmask()Unqualified names never fail on schema USAGE: the search_path lookup simply skips schemas the role cannot use. Row-level security is decided by check_enable_rls() (rls.c): tables without RLS give RLS_NONE; superusers and BYPASSRLS roles (has_bypassrls_privilege()) and the owner without FORCE ROW LEVEL SECURITY give RLS_NONE_ENV (no policies, but the plan depends on the user); otherwise RLS_ENABLED and the policy expressions are prepended to the range table entry's securityQuals.
Privilege bits and ACLs
AclModeis auint64bitmask (ACL_SELECT,ACL_INSERT…ACL_MAINTAINinnodes/parsenodes.h); grant options live in the upper half. An ACL is an array ofAclItem{ai_grantee,ai_grantor,ai_privs}, printed with the letters ofACL_ALL_RIGHTS_STR="arwdDxtXUCTcsAm"(utils/acl.h).- A NULL
relaclmeans "default":acldefault()gives the owner all privileges. The owner can revoke them from itself; only grant options are implicit for the owner (see the comment inaclmask()). pg_class_aclmask_ext()short-circuits forsuperuser_arg()and adds the implicit rights ofpg_read_all_data,pg_write_all_dataandpg_maintainbefore callingaclmask(). Role membership is resolved byhas_privs_of_role()→roles_is_member_of()withROLERECURSE_PRIVS(follows only grants withINHERIT), cached per backend and invalidated bypg_auth_memberschanges.SET ROLEusesROLERECURSE_SETROLEinstead.- The executor gets the required bits per table from
RTEPermissionInfo(requiredPerms,checkAsUser,selectedCols); column privileges are consulted only when the table-level check fails. - GRANT/REVOKE:
ExecuteGrantStmt()→ExecGrant_Relation()→merge_acl_with_grant()/aclupdate();ALTER DEFAULT PRIVILEGESisExecAlterDefaultPrivilegesStmt()storing rows inpg_default_acl.
Hooks and tests
ExecutorCheckPerms_hook(called fromExecCheckPermissions()),object_access_hook(used bycontrib/sepgsql), androw_security_policy_hook_permissive/_restrictiveto inject policies (example:src/test/modules/test_rls_hooks).- Regression tests:
src/test/regress/sql/privileges.sql,rowsecurity.sql,create_role.sql. TrySELECT acldefault('r', 'alice'::regrole)andaclexplode(relacl)to decode ACLs.
Parameters that shape this
Related topics
Client authentication SSL/TLS encryption Logical structure Life of a query