Home › Databases

ERROR: permission denied for schema public — how to fix

ERROR:  permission denied for schema public
LINE 1: CREATE TABLE users (id serial primary key);
                     ^
django.db.utils.ProgrammingError: permission denied for schema public

Quick fix: As a superuser, connected to the right database: GRANT USAGE, CREATE ON SCHEMA public TO myuser; — or make the role own the database and schema: ALTER DATABASE mydb OWNER TO myuser; ALTER SCHEMA public OWNER TO myuser;.

What it means

To create a table you need CREATE on the schema; to read objects in it you need USAGE on the schema (plus privileges on the tables themselves). The role you connect as lacks one of those on public.

The common trigger is PostgreSQL 15+. Older versions granted CREATE on public to every role (PUBLIC). From 15 onward, the public schema is owned by pg_database_owner and only the database owner can create objects there. So this classic setup now breaks migrations:

CREATE DATABASE mydb;
CREATE USER myuser WITH PASSWORD 'secret';
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;   -- does NOT cover schemas

GRANT ALL ON DATABASE only gives CONNECT, CREATE (schemas) and TEMPORARY on the database itself — nothing inside the public schema.

Connect as a superuser (e.g. sudo -u postgres psql):

ALTER DATABASE mydb OWNER TO myuser;

Because public is owned by pg_database_owner on PG 15+, the database owner automatically gets full rights on it. On older versions, also run:

\c mydb
ALTER SCHEMA public OWNER TO myuser;

For new databases, do it at creation time:

CREATE DATABASE mydb OWNER myuser;

Fix 2: grant privileges on the schema

If the role shouldn't own the database, grant explicitly. Connect to the target database first — schema grants are per database, and running them while connected to postgres changes the wrong database.

\c mydb
GRANT USAGE, CREATE ON SCHEMA public TO myuser;

For a read-only or app role that only uses existing tables:

GRANT USAGE ON SCHEMA public TO app_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_reader;
ALTER DEFAULT PRIVILEGES FOR ROLE myuser IN SCHEMA public
  GRANT SELECT ON TABLES TO app_reader;

ALTER DEFAULT PRIVILEGES covers tables created later by myuser.

Fix 3: use a dedicated schema

A clean pattern for shared databases:

CREATE SCHEMA app AUTHORIZATION myuser;
ALTER ROLE myuser SET search_path = app, public;

The role owns its schema and needs nothing on public.

Check what's going on

SELECT current_user, current_database();
\dn+ public                      -- owner and access privileges
SELECT has_schema_privilege('myuser', 'public', 'CREATE');

In \dn+, an ACL entry like myuser=UC/postgres means USAGE (U) and CREATE (C).

Docker and managed databases

That's a table-level privilege, not the schema. Grant it on the table, or change ownership:

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

Sequence privileges are needed for serial / identity inserts (permission denied for sequence users_id_seq).

Last updated 2026-11-02.