ArmorDB Logo
ArmorDB
Fix Postgresql Permission Denied Schema Public
How to Fix PostgreSQL Permission Denied for Schema Public
Back to Blog
Short-Form & Quick Fixes
July 3, 2026
5 min read

How to Fix PostgreSQL Permission Denied for Schema Public

Learn why PostgreSQL returns permission denied for schema public, how to grant the right schema and table privileges, and how to avoid insecure blanket grants.

AE
ArmorDB EngineeringArmorDB engineering
PostgreSQLPermissionsSchemas

The PostgreSQL error permission denied for schema public usually means the role can connect to the database but cannot use the schema where the object lives. That distinction matters: database access, schema access, table privileges, and default privileges are separate checks.

The safest fix is not to make the user a superuser or grant broad ownership. Grant USAGE on the schema, grant the specific table or sequence privileges the application needs, and set default privileges so future objects do not break the next deploy.

Why this error happens

A PostgreSQL schema is a namespace for objects such as tables, views, functions, and sequences. The common public schema is only a default place to create objects; it is not a promise that every role can use everything inside it. To read public.accounts, a role needs access to the database, USAGE on the public schema, and SELECT on the table. To insert rows that use a serial or identity sequence, it may also need sequence privileges.

This changed many teams' assumptions after PostgreSQL 15. The PostgreSQL 15 release notes say the default permissions on the public schema were changed so that ordinary users no longer have CREATE permission there by default. That was a security improvement, but it means old setup scripts and tutorials that relied on public-schema write access can fail in newer clusters.

In managed PostgreSQL, the same rules apply. The provider may create the database and owner role for you, but your application roles still need explicit permissions when you separate migration, application, read-only, and analytics users.

The quick diagnosis

First confirm which role is failing and which schema the query touches. In many incidents, the application connects as a different role than the one used during migrations. The error may also appear after a migration creates a table as one owner while the app runs as another role.

Use a focused check rather than guessing:

SELECT current_user, current_schema();

SELECT table_schema, table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;

If the role cannot even inspect expected objects, test schema visibility directly:

SELECT has_schema_privilege(current_user, 'public', 'USAGE');

That result should be true for a role that needs to reference objects in public. If it is false, grant schema usage before chasing table-level symptoms.

SymptomLikely missing privilegePractical fix
permission denied for schema public on a simple queryUSAGE on the schemaGrant schema usage to the application role
permission denied for table usersTable-level SELECT, INSERT, UPDATE, or DELETEGrant only the operations the role needs
Inserts fail on an ID column backed by a sequenceSequence USAGE or SELECTGrant sequence privileges for generated IDs
New tables fail but old tables workDefault privileges were not configuredSet default privileges for the object owner
Migrations fail creating objects in publicMissing CREATE on the schemaUse a migration owner role or grant CREATE deliberately

The safe fix

Run grants as the database owner or another role allowed to manage privileges. Replace app_user with your application role. If the app only reads data, do not grant write privileges just to make the error disappear.

GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_user;

For a read-only role, keep it narrower:

GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;

These commands fix existing objects. They do not automatically cover tables and sequences created tomorrow by your migration role. For that, set default privileges as the role that will create future objects:

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;

ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app_user;

Default privileges are a common source of repeat incidents. If migrations run as migrator, changing defaults as postgres may not affect objects created by migrator. Apply the command in the same ownership context your deployment pipeline uses.

What not to do

Avoid granting superuser, database ownership, or ALL PRIVILEGES as the first response. Those shortcuts can hide the immediate error while giving the application far more authority than it needs. A web application role usually does not need to create extensions, change ownership, drop unrelated tables, or alter database-wide settings.

Also avoid assuming search_path is the root cause. A wrong search_path can make PostgreSQL look in the wrong schema, but permission denied for schema public is still a permission check. Fix the privilege model, then set an explicit search_path only if your application intentionally relies on unqualified table names.

If you use PgBouncer, remember that pooling does not change PostgreSQL privileges. Test with the same connection string, database name, and role the application uses. ArmorDB users can pair this with the connection guidance in /docs/connect and keep separate roles for migrations, application traffic, and read-only access.

Validate the result

After applying grants, reconnect as the application role rather than testing through an admin session. A new session avoids confusion from cached assumptions in tools or connection pools. Run one read query, one representative write, and one insert that exercises a generated ID. If the application uses migrations, create a small table in staging and confirm the app role can access it after default privileges are in place.

The durable fix is a short permissions runbook: name the owner role, the migration role, the runtime role, and the grants each one receives. That makes the next database, branch preview, or staging clone behave like production instead of rediscovering the same error.

Sources and further reading

Topic

Short-Form & Quick Fixes

Updated

Jul 3, 2026

Read time

5 min read

About the author

ArmorDB Engineering writes about PostgreSQL operations, security, and infrastructure decisions for teams building production apps on ArmorDB.