Incident brief
Permission denied
A role tried to use a table it has not been granted privileges on. PostgreSQL denied the action instead of falling back to the owner's or a broader role's access.
What lands in your log
ERROR: permission denied for table secrets
In 10 seconds
- What triggers it
- Create a table as its owner, then switch to a role with no privileges granted on it.
- Fix
- GRANT the specific privilege (e.g. SELECT) on the table to the role that needs it.
- Proof
- Reproduced on PostgreSQL 16.14 → A role with no privileges on a table was rejected with SQLSTATE 42501 when it tried to SELECT from it. The table's data was confirmed unaffected afterward.
Fix
What to do right now
Application-level steps for this error.
- GRANT the specific privilege (e.g. SELECT) on the table to the role that needs it.
- Grant only what's needed, column-level GRANTs are available if a role should see some columns but not others.
- Don't assume ALTER DEFAULT PRIVILEGES retroactively fixes access to existing tables, see the premium test for its actual scope.
GRANT SELECT ON vault.secrets TO analyst;For this error
See this error live on the server
Run these against the affected instance to confirm the diagnosis before you act.
Check the effective login role, inherited memberships, and privileges on the named relation; do not grant blindly to PUBLIC.
Effective table privileges
Replace the relation literal from the error.
SELECT current_user, session_user,
has_table_privilege(current_user, to_regclass('<schema.table>'), 'SELECT') AS can_select,
has_table_privilege(current_user, to_regclass('<schema.table>'), 'INSERT') AS can_insert,
has_table_privilege(current_user, to_regclass('<schema.table>'), 'UPDATE') AS can_update,
has_table_privilege(current_user, to_regclass('<schema.table>'), 'DELETE') AS can_delete;Inherited role memberships
Show which granted roles can contribute privileges to the current user.
SELECT r.rolname AS granted_role,
pg_has_role(current_user, r.oid, 'USAGE') AS inherited
FROM pg_roles r
WHERE pg_has_role(current_user, r.oid, 'MEMBER')
ORDER BY r.rolname;Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual, not paraphrased.
PostgreSQL 16 Documentation, §5.8 Privileges
For most kinds of objects, the initial state is that only the owner (or a superuser) can do anything with the object. To allow other roles to use it, privileges must be granted.Read the full section on postgresql.org →
The denied SELECT
analyst was created with no privileges granted on vault.secrets. Per the manual, only the owner (or a superuser) can act on a new object by default, so PostgreSQL denied the read.Confirming the data is untouched
The row inserted during setup is still there, the denied SELECT changed nothing.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab. The commands below are exactly what was executed.
- 1Create a table as its owner, then switch to a role with no privileges granted on it.
- 2That role attempts to SELECT from the table.
- 3SQLSTATE 42501 is reported, naming the table.
One client: create a table and a no-privilege role, then SET ROLE and try to read it.
CREATE SCHEMA IF NOT EXISTS vault;
DROP TABLE IF EXISTS vault.secrets;
CREATE TABLE vault.secrets (id serial PRIMARY KEY, secret text);
INSERT INTO vault.secrets (secret) VALUES ('classified');
DROP ROLE IF EXISTS analyst;
CREATE ROLE analyst LOGIN;
GRANT USAGE ON SCHEMA vault TO analyst;SET ROLE analyst;
SELECT * FROM vault.secrets;
RESET ROLE;RESET ROLE;
SELECT count(*) FROM vault.secrets;What PostgreSQL actually returned
DROP TABLE
CREATE TABLE
INSERT 0 1
DROP ROLE
CREATE ROLE
GRANTSET
ERROR: permission denied for table secrets
RESETRESET
count
-------
1
(1 row)The GRANT fix was tested directly against the same role and table.
Without this
Above: analyst has no privilege on vault.secrets and is denied.
With this, tested
Below: after GRANT SELECT, the same role can read the table.
- A second operational test: exact SQL, raw output, measured result, and engineer notes
- Fix that looks broader but isn't retroactive: exact SQL, output, and verdict
- A manual-grounded production interpretation of the lab result
Card required. Cancel before day 7 and you are not charged.
Runbook to fix this
Runbooks for this incident
Full step-by-step fixes for the condition behind this error: the diagnosis, the exact SQL, and output captured in the lab.
Connected
Everything this error touches
Every page this SQLSTATE connects to: the concept that explains it, the runbooks that fix it, the parameters you tune to prevent it, and the sibling errors it travels with. All real cross-references. Jump straight in, or open the full interactive map.
Fix it — runbooks
Related errors
Verification
- Last verified
- 2026-07-15 (Docker lab, PostgreSQL 16.14)
- Verification scope
- Verified against PostgreSQL 16.14 in an isolated lab environment
- Audit status
- reviewed
Went further?
Pro unlocks the second lab proof
Free page stops the bleeding. Pro adds the operational test, SQLSTATE audit, and deeper evidence, same error, more certainty.