If a script that worked on PostgreSQL 14 suddenly throws permission denied for schema public on PostgreSQL 15 or newer, you did nothing wrong - the default changed. Before PG15, every user could create objects in the public schema. From PG15 on, that permission was removed for security, so a non-owner user now needs an explicit grant. The fix is one statement run as a superuser or the database owner: GRANT ALL ON SCHEMA public TO your_user; (or the narrower USAGE, CREATE). This is the single most common cause of that error, and knowing the version change is the difference between a ten-second fix and an hour of confusion.
The error text points at a permission, and people go hunting through roles and table grants. The real answer is usually a one-line release-note change that nobody read.
Table of contents
- The PostgreSQL 15 change that causes it
- The fix: grant the schema privileges
- Getting the privileges right, not just broad
- Table privileges are a separate grant
- Avoiding it on managed Postgres
- How this fits the rest of the stack
- FAQ
The PostgreSQL 15 change that causes it
For years, the public schema in a new PostgreSQL database granted CREATE to everyone (the PUBLIC role). Convenient, and a security footgun - any user could create objects in it.
PostgreSQL 15 closed that. In a database created on PG15 or later, the public schema no longer grants CREATE to all users by default. Only the schema owner (and superusers) can create objects there. So a perfectly ordinary application user that used to run CREATE TABLE now hits:
ERROR: permission denied for schema public
Nothing about your user or your script changed. The default behaviour of the schema did. This is why the error appears the moment you upgrade to PG15, or spin up a new PG15+ instance, and run migrations that used to just work.
The fix: grant the schema privileges
Connect as a superuser (often postgres) or the database owner, and grant the application user what it needs on the public schema:
GRANT ALL ON SCHEMA public TO myuser;
ALL on a schema means USAGE (permission to look inside it) plus CREATE (permission to make objects in it). If you prefer least privilege, be explicit:
GRANT USAGE ON SCHEMA public TO myuser;
GRANT CREATE ON SCHEMA public TO myuser;
USAGE alone lets the user reference existing objects but not create new ones - which is exactly what you want for a read-only reporting user. CREATE is what migrations and schema-building need. Grant CREATE only to the users that actually build tables.
Getting the privileges right, not just broad
Reaching for GRANT ALL makes the error disappear, but it is worth spending ten seconds on what the user should actually have:
- A migration / schema-owner user needs
CREATE(to build tables) plusUSAGE.GRANT ALL ON SCHEMA publiccovers it. - A normal application user that only reads and writes existing tables needs
USAGEon the schema, plus privileges on the tables - notCREATEon the schema. It should not be creating tables at runtime. - A read-only reporting user needs
USAGEplusSELECTon tables, nothing more.
The cleaner design is to have one owner role that builds the schema and separate, less-privileged roles that use it. Granting CREATE to every application user reproduces the exact footgun PG15 was closing. Fix the error, but do it with the privilege the user genuinely needs.
Table privileges are a separate grant
A subtle follow-on: granting on the schema is not the same as granting on the tables inside it. After fixing the schema permission, a user may still hit permission denied for table ... because table access is granted separately.
To let a user read and write existing tables:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myuser;
And because that only covers tables that exist right now, set a default so future tables are covered too:
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO myuser;
The schema grant lets the user into the schema; the table grants let it do things with the objects there. People fix the schema error, then trip over the table error five minutes later - handle both in one go.
Avoiding it on managed Postgres
On a managed PostgreSQL service you usually connect as a provisioned admin user that is the database owner, so you may never see this error for your own objects. It shows up when you create additional application users and expect them to inherit the old open-schema behaviour - which no longer exists on PG15+.
The clean pattern, whether self-hosted or managed:
- Create the application role.
- Grant it
USAGEonpublic, andCREATEonly if it runs migrations. - Grant table privileges (and default privileges) for the tables it uses.
Bake those grants into your migration or provisioning scripts so a fresh PG15+ database is set up correctly from the start, rather than patching the error by hand each time you add a user. It is three statements, and it turns a recurring surprise into a solved problem.
How this fits the rest of the stack
Whatever you decide here, the cost of it eventually shows up as a bill. The RunxBuild hosting calculator is the right place to model that before committing: the compute, the database, the storage, the bandwidth, the worker - each one is a separate line item, and the real cost of a platform is the sum, not the headline number. The RunxBuild dashboard is where the team sees the actual usage once it is running.
Useful related references:
- SSH Permission Denied: Publickey, Password, and the Real Cause
- SSH Public Key Denied: 8 Things to Check
- NFS Access Denied by Server: 6 Causes and the Right Fixes
- Database user management on RunxBuild
FAQ
What causes permission denied for schema public in PostgreSQL?
Most often the PostgreSQL 15 change that removed the default CREATE privilege on the public schema. Before PG15 any user could create objects in public; from PG15 on, a non-owner user needs an explicit grant. A script that worked on PG14 hits this error the moment it runs against PG15 or later.
How do I fix permission denied for schema public?
Connect as a superuser or the database owner and run GRANT ALL ON SCHEMA public TO myuser;, or the narrower GRANT USAGE, CREATE ON SCHEMA public TO myuser;. USAGE lets the user reference the schema and CREATE lets it make objects. Grant CREATE only to users that actually build tables.
What is the difference between USAGE and CREATE on a schema?
USAGE lets a user look inside the schema and reference existing objects; CREATE lets a user make new objects (tables, views) in it. A read-only or normal application user needs only USAGE plus table privileges, while a migration user that builds the schema needs CREATE as well.
Why do I still get permission denied for table after granting the schema?
Because schema and table privileges are separate. Granting on the schema lets a user in; you also need GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO myuser; for existing tables, plus ALTER DEFAULT PRIVILEGES so future tables are covered automatically.
Did PostgreSQL 15 change schema permissions?
Yes. PostgreSQL 15 removed the default CREATE privilege that the public schema previously granted to all users, for security. In new PG15+ databases only the schema owner and superusers can create objects in public by default, so other users must be granted the privilege explicitly.