Zum Inhalt springen

Sicherheitsgrundlagen

Supabase "permission denied for schema public": drei Ursachen

Supabase "permission denied for schema public" heißt: Ein Grant auf das Schema fehlt. Was ihn entfernt hat und welche Zeile ihn zurückbringt.

Vlad Tkachenko11 Min. Lesezeit
Ein ummauerter Hof mit Datenbanktabellen, jede Tür darin offen und hell, hinter einem Eingangstor, das mit gesenkten Gitterstäben zu ist.

Kurz gesagt

  • Supabase "permission denied for schema public" bedeutet, dass die Rolle hinter der Anfrage das Schema public überhaupt nicht betreten darf. Postgres hat also abgelehnt, bevor es sich auch nur eine Tabelle angesehen hat.
  • Jedes Supabase-Projekt startet mit diesem Recht für anon, authenticated und service_role. Bekommt deine App diesen Fehler, hat etwas es entfernt: Das Schema wurde gelöscht und neu angelegt, oder ein Skript hat den Grant entzogen.
  • Zurückholen lässt er sich mit einer Zeile SQL. Der Grant lässt eine Rolle nur bis zu deinen Tabellen, welche Zeilen ein Besucher bekommt, entscheidet weiterhin Row Level Security. Prüf diese Regeln, bevor du es für erledigt hältst.

Gestern lief deine App noch. Heute bleibt jeder Bildschirm, der etwas aus Supabase liest, leer oder zeigt einen Fehler, und in der Browserkonsole steht bei jedem dieselbe Zeile: permission denied for schema public. Du öffnest das Supabase-Dashboard, und alle Tabellen sind noch da. Der SQL Editor liest sie ohne jede Beschwerde.

Die Antwort hinter dieser Zeile kommt meist als 401 oder 403. Auf die zwei Felder gekürzt, auf die es ankommt, sieht sie so aus:

{
  "code": "42501",
  "message": "permission denied for schema public"
}

Supabase "permission denied for schema public" kommt von Postgres, der Datenbank, auf der Supabase läuft, und die Liste der Ursachen ist kurz. Jedes Supabase-Projekt wird mit einem Grant angelegt, der deine App ins Schema public lässt. Jemand oder etwas hat ihn entfernt, und zurückholen lässt er sich mit einer Zeile SQL.

Hier ist der Teil, den man leicht verkehrt herum versteht: Dieser Grant ist die Eingangstür des Gebäudes, und die Schlösser für deine Daten liegen weiter drinnen. Wenn ihn jemand entfernt hat, um Fremde draußen zu halten, hat er deine App gleich mit ausgesperrt. Und ob es sicher ist, die Tür wieder zu öffnen, hängt an diesen inneren Schlössern. Sie sind es, was du prüfen solltest, bevor du die Sache für erledigt hältst.

Was bedeutet "permission denied for schema public"?

Es bedeutet, dass die Rolle hinter der Anfrage das Schema public nicht betreten darf. Postgres hat also abgelehnt, bevor es sich auch nur eine Tabelle angesehen hat.

Ein Schema ist ein benannter Ordner mit Tabellen in deiner Datenbank. public ist der, in den Lovable, Bolt, v0, Cursor, Claude Code und Replit deine Tabellen legen, solange ihnen niemand etwas anderes sagt. Eine Rolle ist die Art von Besucher, von der eine Anfrage kommt: anon ist jemand, der nicht angemeldet ist, authenticated jemand, der angemeldet ist, und service_role dein eigener Servercode mit dem geheimen Schlüssel. Ein Grant gibt einer Rolle ein Recht auf eine Sache.

Das Recht, um das es bei diesem Fehler geht, heißt USAGE, und die Dokumentation von Postgres beschreibt es als das Recht, Objekte innerhalb des Schemas nachzuschlagen ("look up"). Eine Rolle ohne dieses Recht erreicht keine Tabelle in diesem Schema, ganz gleich, was in den Einstellungen der Tabelle selbst steht.

Wenn du dir durch die Tabellen-Variante dieses Fehlers angewöhnt hast, jede Tabelle als Raum mit einer Tür pro Rolle zu sehen, dann ist das Schema das Gebäude, in dem diese Räume liegen, und USAGE ist seine Eingangstür. permission denied for schema public heißt, dass deine Anfrage draußen vor dieser Tür steht. Welche Räume ihre Türen offen haben, spielt keine Rolle, denn bis zu einem Raum kommt die Anfrage nie.

Supabase gibt diese Eingangstür allen drei Rollen, wenn ein Projekt angelegt wird. Das eigene Datenbank-Image von Supabase führt diese Zeile in seinem ersten Setup-Skript aus:

grant usage on schema public to postgres, anon, authenticated, service_role;

Wenn deine App diesen Fehler bekommt, war der Grant also einmal da und ist jetzt weg.

Warum funktioniert der SQL Editor trotzdem?

Weil der SQL Editor keine der Rollen deiner App benutzt. Ein Troubleshooting-Artikel von Supabase sagt, dass eine Abfrage von dort "routed through the postgres role at the database level" wird, also auf Datenbankebene über die Rolle postgres läuft.

postgres ist die Rolle, der deine Tabellen gehören, und wenn das Schema selbst über das Dashboard oder von deinem Builder neu angelegt wurde, gehört ihr auch das. Ein Eigentümer braucht keinen Grant, um sein eigenes Gebäude zu betreten. Also zeigt das Dashboard jede Tabelle, ein select im SQL Editor liefert jede Zeile, und dieselbe Anfrage aus deiner App wird an der Eingangstür abgewiesen. Nichts auf diesen Bildschirmen verrät dir, dass die Rollen deiner App den Weg hinein verloren haben.

Um zu sehen, was deine App sieht, frag Postgres direkt nach ihren Rollen. Füg das in den SQL Editor ein:

select rolname,
       has_schema_privilege(rolname, 'public', 'USAGE') as can_enter
from pg_roles
where rolname in ('anon', 'authenticated', 'service_role');

Jede Zeile sollte true zeigen. Eine Zeile mit false ist eine Rolle, die deine App benutzt und die nicht mehr hineinkommt.

Der Grant öffnet das Gebäude, die Schlösser liegen drinnen

USAGE lässt eine Rolle bis zu den Tabellen in einem Schema. Es sagt nichts darüber, welche dieser Tabellen die Rolle benutzen darf oder welche Zeilen sie zurückbekommt.

Hinter der Eingangstür stehen zwei weitere Prüfungen, und beide gehören zur jeweiligen Tabelle. Der eigene Grant der Tabelle entscheidet, ob eine Rolle diese Tabelle überhaupt benutzen darf, und ein fehlender ist das, was permission denied for table meldet. Danach entscheidet Row Level Security, die Regeln pro Zeile, die du vielleicht schon kennst, welche Zeilen diese Rolle zurückbekommt. Postgres fragt alle drei in dieser Reihenfolge ab und hört bei der ersten Ablehnung auf.

Postgres prüft das Schema, dann die Tabelle, dann die Zeilen, und hört bei der ersten Ablehnung auf. Ein geschlossenes Tor weist eine Anfrage ab, bevor die Tür irgendeiner Tabelle überhaupt versucht wird.

Genau diese Reihenfolge macht den Grant auf das Schema zu einem schlechten Schloss. Für eine Rolle heißt er alles oder nichts: Nimm ihn anon weg, und jeder Bildschirm, den ein Besucher ohne Anmeldung sieht, hört auf einmal auf zu funktionieren, auch eine Anmeldeseite, die irgendetwas aus deinen Tabellen liest. Nimm ihn authenticated weg, und dasselbe passiert allen Angemeldeten. Die Dokumentation von Postgres fügt hinzu, dass sein Entzug "is not a completely secure way to prevent object access", also kein vollständig sicherer Weg ist, den Zugriff zu verhindern, und dass eine Rolle ohne ihn die Namen deiner Tabellen weiterhin aus den Systemkatalogen lesen kann.

Wenn du diesen Fehler überhaupt siehst, braucht deine App die Rolle, die abgewiesen wurde.

Ursache 1: Das Schema wurde gelöscht und neu angelegt

Diese Ursache nimmt am meisten mit. Jemand hat diese zwei Zeilen ausgeführt oder etwas, das dasselbe tut:

drop schema public cascade;
create schema public;

Die erste Zeile entfernt das Schema und wegen cascade jede Tabelle, jede View und jede Funktion darin. Die zweite legt ein neues, leeres public an. Ein neues Schema gehört der Rolle, die es angelegt hat, und die Dokumentation von Postgres sagt klar, dass sonst niemand standardmäßig irgendein Recht auf ein Schema bekommt. Der Grant, den Supabase beim Anlegen deines Projekts ausgeführt hat, gehörte zum alten Schema und ist mit ihm verschwunden, und Supabase führt sein Setup-Skript kein zweites Mal aus.

Diese zwei Zeilen sind der kürzeste Weg, eine Postgres-Datenbank zu leeren. Ein KI-Assistent greift deshalb womöglich zu ihnen, wenn du ihn bittest, die Datenbank neu aufzusetzen oder eine fehlschlagende Migration sauber durchlaufen zu lassen.

Danach kommen deine Tabellen zurück, wenn die Migrationen der App erneut laufen. Eine Migration ist eine der Dateien mit Datenbankänderungen, die dein Builder schreibt, und wenn alle laufen, bauen sie die Tabellen wieder auf, die sie beschreiben. Was nicht zurückkommt, ist alles, was sie nie erwähnen, und den Grant für die Eingangstür hat Supabase angelegt, bevor deine erste Migration existierte. In Migrationen, die dein Builder geschrieben hat, kommt er womöglich gar nicht vor.

Zwei weitere Dinge verschwinden mit dem alten Schema:

  • Die automatischen Grants für neue Tabellen. Postgres speichert sie als Standardrechte (default privileges) eines Schemas, und sie werden zusammen mit dem Schema gelöscht. Tabellen, die danach entstehen, haben für keine der Rollen deiner App einen Grant.
  • Die Zeilen. Migrationen bauen leere Tabellen. Was in den alten stand, ist nur dann wieder da, wenn jemand eine Kopie eingespielt hat, die vor dem Reset entstanden ist.
Vor dem Reset konnten alle vier Rollen das Schema betreten. Danach lassen sich die Tabellen neu bauen, aber das Tor und ihre Türen öffnen sich nur für postgres, die Rolle, die sie gebaut hat.

Wenn deine Tabellen zurück und leer sind, kommt die Suche nach dieser Kopie vor allem anderen auf dieser Seite.

Ursache 2: Ein Entzug, der absichern sollte

Der zweite Weg ist Absicht. Jemand hat gelesen, dass jede Tabelle in public über die Data API von Supabase erreichbar ist, also über die Webadresse, über die deine App ihre Daten liest und schreibt, und hat den Grant auf das Schema entzogen, um das zu schließen.

Die Sorge dahinter ist berechtigt, das gewählte Werkzeug ist das falsche. Der eigene Leitfaden von Supabase zum Absichern der Data API fasst den Grant auf das Schema nie an. Er entzieht den automatischen Zugriff, den neue Tabellen in public bekommen, sodass jede neue Tabelle zu bleibt, bis jemand ihr absichtlich Zugriff gibt. Supabase schaltet das am 30. Oktober 2026 für jedes bestehende Projekt ein.

Hat ein Skript, das du ausgeführt hast, oder ein Assistent, den du um mehr Sicherheit gebeten hast, den Grant auf das Schema entfernt, zeigt dir die Prüfabfrage oben, welche Rollen ihn verloren haben. Gib ihn den Rollen zurück, die deine App benutzt, und sichere dann so ab, wie es die Abschnitte weiter unten beschreiben.

Ursache 3: Der Fehler kommt aus einer Migration

Erscheint die Meldung, während etwas Tabellen anlegt, etwa eine Migration, ein Deployment oder Prisma oder Drizzle beim Einrichten deiner Datenbank, dann fehlt ein anderes Recht. Um irgendetwas in einem Schema anzulegen, braucht es CREATE darauf, und die Fehlermeldung für ein fehlendes CREATE ist dieselbe Zeile.

Seit Postgres 15 bekommen gewöhnliche Rollen standardmäßig kein CREATE mehr auf public. Die Dokumentation von Postgres sagt, dass nur Datenbanken, die von Version 14 oder älter aktualisiert wurden, es noch allen geben. Ein eigener Datenbanknutzer für ein Migrationswerkzeug wird deshalb schon bei seiner allerersten Tabelle abgewiesen. Der Prisma-Leitfaden von Supabase legt einen Nutzer prisma an, und zu seinem Setup gehört grant create on schema public to prisma.

Die Lösung ist ein create-Grant für genau diesen einen Migrationsnutzer. Niemand, der deine App besucht, muss Tabellen anlegen, deshalb bekommen anon und authenticated nie einen.

So holst du den Grant sicher zurück

Führ eine Zeile aus, prüf, dass alle drei Rollen hineinkommen, lad dann deine App neu und lies, was sie als Nächstes sagt.

Die Zeile, im SQL Editor:

grant usage on schema public to anon, authenticated, service_role;

Bleib bei usage. grant all on schema public beseitigt den Fehler auch, und auf einem Schema heißt all so viel wie usage plus create, also das Recht, eigene Tabellen hinzuzufügen. Supabase rät, jeder Rolle nur das Nötigste zu geben, und die Rollen deiner Besucher müssen nur hineinkommen.

Die Prüfabfrage von oben sollte jetzt für alle drei Rollen true zeigen. Lad deine App neu, und eins von zwei Dingen passiert:

  • Sie funktioniert. Die Eingangstür war das Einzige, was gefehlt hat.
  • Eine neue Meldung nennt eine Tabelle. permission denied for table heißt, dass auch der eigene Grant dieser Tabelle fehlt. Damit ist nach der ersten Ursache zu rechnen, denn neu gebaute Tabellen haben die automatischen Grants nie bekommen. Gib jeder Tabelle ihren Grant, Row Level Security und eine Regel in einer Änderung.

Widersteh der Versuchung, jeder Rolle alles auf jeder Tabelle zu geben, damit die zweite Meldung verschwindet. Eine Tabelle mit Grant an anon und ausgeschalteter Row Level Security gibt jede Zeile an jeden heraus, der danach fragt, und in deiner App sieht nichts anders aus.

Nennt die Meldung ein anderes Schema, etwa permission denied for schema api, dann hat jemand dieses Schema selbst angelegt, und es hat nie die Grants bekommen, mit denen public startet. Der Leitfaden von Supabase zu eigenen Schemas listet die Zeilen auf, die es braucht.

Was du stattdessen absichern solltest

Die Mittel, die Fremde von deinen Daten fernhalten, gehören zu jeder einzelnen Tabelle, und der Sicherheitsleitfaden von Supabase nennt sie:

  1. Ein Grant pro Tabelle, für die Rollen, die ihn brauchen. Eine Tabelle mit Bestellungen braucht für anon überhaupt keinen Grant.
  2. Row Level Security auf jeder Tabelle, mit einer Regel.Wie du sie einschaltest und prüfst, ob sie hält.
  3. Neue Tabellen standardmäßig zu. Das Entziehen der automatischen Grants erledigt das schon heute, und der 30. Oktober erledigt es für dich.
  4. Interne Tabellen außerhalb der Data API. Der Leitfaden von Supabase beschreibt, das, was deine App liest, in ein eigenes Schema zu legen, sodass alles andere in Schemas liegt, die die Data API nicht ausliefert.

Schau dir das Ergebnis danach von außen an, denn keiner dieser Bildschirme zeigt dir, was ein Fremder zurückbekommt. Unser kostenloser Scan fragt jede Tabelle, die deine App nennt, wie viele Zeilen ein Besucher ohne Anmeldung bekäme, liest die Zahl, ohne eine einzige Zeile abzurufen, und dauert ohne Konto etwa 20 Sekunden: scanne deine App.

Deine Tabellen behalten, und was in ihnen steht

Nach heute kann noch zweierlei schiefgehen: Eine spätere Änderung öffnet eine Tabelle für Fremde, und ein Reset wie bei der ersten Ursache nimmt deine Zeilen mit.

Reeve Monitor prüft weiter von außen:

  • die vollständige Sicherheitsprüfung jede Stunde, für bis zu drei Apps
  • eine Nachricht, wenn sich ein Ergebnis ändert, damit eine Tabelle, die nach einer Migration aufgegangen ist, nicht wartet, bis du hinschaust
  • ob deine App erreichbar ist
  • ein monatlicher Bericht in einfacher Sprache über das, was er gesehen hat

Reeve Care hält eine Kopie von dem, was in deinen Tabellen steht:

  • eine verschlüsselte Kopie deiner Supabase-Datenbank nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
  • jede Kopie geprüft, bevor sie zählt, durch Zählen der Zeilen in jeder Tabelle
  • auch deine hochgeladenen Dateien, sobald du einen Storage-Zugang verbindest
  • eine Wiederherstellung per Klick, mit einer Sicherheitskopie des aktuellen Stands vorab
  • alles, was Monitor tut

Was du jetzt tun solltest

Was zu tun ist

  • Führ die Prüfabfrage im SQL Editor aus. Jede Rolle mit false ist eine, die deine App verloren hat.
  • Hol den Grant mit grant usage on schema public to anon, authenticated, service_role; zurück und bleib bei usage.
  • Wurde das Schema gelöscht, sieh nach, ob deine Tabellen leer sind, und such eine Kopie von vor dem Reset.
  • Folgt ein Tabellen-Fehler, gib dieser Tabelle ihren Grant, Row Level Security und eine Regel zusammen.
  • Sichere pro Tabelle ab, wo es sich fein einstellen lässt, und lass den Grant auf das Schema in Ruhe.
  • Kam der Fehler aus einer Migration, gib create dem Migrationsnutzer und sonst niemandem.

Fang mit der Prüfabfrage an, denn ein einziges Ergebnis sagt dir, welche Rollen deiner App den Weg hinein verloren haben. Und wenn die Tür wieder offen ist, scanne deine App, um zu sehen, was sie einem Fremden gibt.

FAQ

Was bedeutet "permission denied for schema public" in Supabase?

Es bedeutet, dass die Rolle hinter der Anfrage, meist anon oder authenticated, das Schema public nicht betreten darf. Postgres hat also abgelehnt, bevor es irgendeine Tabelle angesehen hat. Der Fehler trägt den Code 42501 und erreicht deine App meist als 401 oder 403. Jedes Supabase-Projekt startet mit diesem Recht, der Fehler heißt also, dass jemand oder etwas es entfernt hat.

Ist es sicher, usage auf das Schema public zu vergeben?

Ja. Jedes Supabase-Projekt startet mit genau diesem Grant. Er lässt eine Rolle nur bis zu den Tabellen im Schema: Jede Tabelle braucht weiterhin ihren eigenen Grant, und Row Level Security entscheidet weiterhin, welche Zeilen jeder Besucher bekommt. Einen Blick verdienen die Tabellen dahinter, denn eine mit Grant an anon und ausgeschalteter Row Level Security gibt jede Zeile an jeden heraus, der danach fragt.

Was ist der Unterschied zu "permission denied for table"?

Der Schema-Fehler stoppt eine Anfrage, bevor sie irgendeine Tabelle erreicht, deshalb schlagen alle Tabellen im Schema auf einmal fehl. Der Tabellen-Fehler betrifft eine einzelne Tabelle, die für diese Rolle keinen Grant hat, und der Rest deiner App läuft weiter. Beide tragen den Code 42501, unterscheiden lassen sie sich also nur am Wortlaut der Meldung.

Warum funktionieren meine Tabellen im Supabase SQL Editor, aber nicht in meiner App?

Weil der SQL Editor deine Abfragen als Rolle postgres ausführt. Ihr gehören deine Tabellen, und sie braucht den Grant nicht, den die Rollen deiner App verloren haben. Deine App benutzt anon für Besucher ohne Anmeldung und authenticated für angemeldete. Frag Postgres mit has_schema_privilege direkt nach diesen beiden Rollen, dann siehst du, was deine App sieht.

Was sollte ich stattdessen absichern?

Die Tabellen. Gib jeder Tabelle nur für die Rollen einen Grant, die ihn brauchen, schalte Row Level Security mit einer Regel auf jeder Tabelle ein und lass neue Tabellen ganz ohne Zugriff starten. Diesen letzten Teil macht Supabase am 30. Oktober 2026 für bestehende Projekte zum Standard.

Geschrieben von

Vlad Tkachenko

Gründer, Reeve

Ich schaue mir den ganzen Tag Apps an, die mit Lovable, Bolt, v0, Cursor und Replit gebaut wurden, und die kurze Liste von Fehlern, die darin immer wieder auftauchen.

Mehr über den Autor

Weiterlesen

Alle Artikel

Sichere deine Supabase-Datenbank vor der nächsten Änderung

Reeve sichert sie verschlüsselt nach Zeitplan und stellt sie für dich wieder her, wenn etwas schiefgeht.

Supabase-Backups ansehen

Automatisierte externe Prüfung, kein vollständiges Audit. Das Fehlen von Funden ist keine Garantie für Sicherheit.