Sicherheitsgrundlagen
"Edge Function returned a non-2xx status code": der echte Fehler
"Edge Function returned a non-2xx status code" heißt: Deine Supabase-Funktion hat mit einem Fehler geantwortet. So liest du den echten.

Kurz gesagt
- "Edge Function returned a non-2xx status code" sagt supabase-js zu jeder Antwort deiner Funktion außerhalb der 200er. Der echte Grund kam in derselben Antwort zurück, und die Logs der Funktion enthalten den Rest.
- Lies zuerst die Statusnummer. 401 heißt, die Anfrage wurde abgewiesen, bevor dein Code lief, 404, dass keine Funktion dieses Namens deployt ist, 500, dass dein Code gescheitert ist, 546 oder 504, dass Ressourcen oder Zeit ausgingen.
- Zwei schnelle Lösungen lassen die Meldung verschwinden und reißen ein Loch: die API aus deinem Frontend aufrufen, mit dem Schlüssel darin, und die JWT-Prüfung abschalten, ohne eine eigene Prüfung an ihre Stelle zu setzen.
Du drückst in deiner App den Knopf, der eine Edge Function aufruft, und statt eines Ergebnisses zeigt deine App das hier:
Edge Function returned a non-2xx status code
Es ist derselbe Satz, egal was schiefging. Ein fehlender Schlüssel, eine abgestürzte Funktion, eine Anfrage, die abgewiesen wurde, bevor dein Code lief: Alle lauten "Edge Function returned a non-2xx status code", also sagt dir die Meldung allein fast nichts darüber, was zu reparieren ist.
Was sie dir nicht sagt: Der Grund ist schon angekommen. Der Fehler kam in derselben Antwort zurück wie dieser Satz, und supabase-js liest ihn dir nicht vor. Wer den Satz sucht, findet die Vermutungen anderer Leute. Wer die Antwort liest, findet seine eigene.
Was bedeutet "Edge Function returned a non-2xx status code"?
Deine Funktion hat geantwortet, und die Antwort war ein Fehler. Mehr sagt der Satz nicht.
Jede Antwort im Web trägt einen Statuscode, eine dreistellige Zahl, die sagt,
wie die Anfrage gelaufen ist. Alles in den 200ern heißt, es hat geklappt. Wenn
deine App eine Funktion über supabase.functions.invoke aufruft, schaut
supabase-js auf diese Zahl, und liegt sie außerhalb der 200er, bekommt deine App
statt der Daten einen festen Satz. Der Satz steht wörtlich in
supabase-js selbst,
und der Fehler, der ihn trägt, heißt FunctionsHttpError.
Stell dir den Aufruf als Brief vor, den du an ein Büro im Hinterhaus schickst. Das Büro beantwortet entweder, was du gefragt hast, oder schickt den Brief zurück. Kommt er zurück, liest supabase-js den Stempel auf dem Umschlag und sagt dir, dass er zurückkam. Den Umschlag öffnet es nicht. Die Notiz darin sagt, warum, und das Büro hat ein Logbuch über alles geführt, was mit deinem Brief passiert ist.
Zwei nahe Verwandte sehen ähnlich aus und bedeuten etwas anderes. Failed to send a request to the Edge Function heißt, dass überhaupt keine lesbare Antwort
zurückkam, und im Browser ist das oft ein fehlender CORS-Header, den
der Beitrag zu dieser Meldung
behandelt. Und wenn mehrere voneinander unabhängige Teile deiner App im selben
Moment kaputtgingen, prüf zuerst,
ob es an Supabase oder an deiner App liegt,
bevor du irgendeinen Code öffnest.
Wo steht der echte Fehler?
An zwei Stellen, und du kannst beide lesen, ohne eine Zeile Code zu ändern.
Die Antwort selbst. Öffne deine Live-App in Chrome, Edge oder Firefox, drück
F12, wähl den Tab Network und wiederhole, was fehlgeschlagen ist. Klick die
Anfrage an, deren Adresse /functions/v1/ gefolgt vom Namen deiner Funktion
enthält. Ihre Spalte Status zeigt die Zahl, und ihr Tab Response zeigt,
was die Funktion mitgeschickt hat: eine Meldung, die dein eigener Code
geschrieben hat, ein kurzer JSON-Inhalt von der Plattform von Supabase oder die
Worte Internal Server Error.
Wenn du oder das Werkzeug, das deinen Code schreibt, den Aufruf ändern kann,
zeigt der
Leitfaden zur Fehlerbehandlung von Supabase,
wie man dieselbe Antwort in der App liest, denn die ursprüngliche Antwort reist
mit dem Fehler als error.context:
const { data, error } = await supabase.functions.invoke('your-function', { body })
if (error) console.log(error.context.status, await error.context.text())
Das Logbuch der Funktion. Öffne im Supabase-Dashboard Edge Functions,
wähl die Funktion und lies zwei Tabs. Invocations listet jede Anfrage mit
ihrem Statuscode und ihrer Dauer. Logs enthält, was der
Logging-Leitfaden von Supabase
Plattform-Ereignisse, nicht abgefangene Ausnahmen und eigene Log-Meldungen
nennt, also alles, was dein Code mit console.log oder console.error
ausgegeben hat. Lös den Fehler einmal aus, merk dir die Uhrzeit und lies beide
Tabs zu dieser Minute.
Lies zuerst den Statuscode
Die Zahl sagt dir, wie weit deine Anfrage kam, bevor sie zurückgeschickt wurde, und das entscheidet, wo der Grund aufgeschrieben ist.
| Status | Was passiert ist | Wo der Grund steht |
|---|---|---|
401 | Abgewiesen, bevor dein Code lief, oder dein Code hat abgewiesen | Die Antwort. Invalid JWT oder Missing authorization header ist die Plattform |
404 | In diesem Projekt ist keine Funktion mit diesem Namen deployt | Die Antwort lautet Requested function was not found |
500 | Dein Code lief und hat einen Fehler geworfen oder absichtlich 500 geliefert | Der Tab Logs |
503 | Die Funktion ist gar nicht erst gestartet | Der Tab Logs: ein Import, ein Syntaxfehler oder die Konfiguration der Funktion |
504 | Keine Antwort innerhalb von 150 Sekunden | Der Tab Invocations, dann das, worauf dein Code gewartet hat |
546 | CPU-Zeit oder Speicher sind ausgegangen | Der Tab Logs sagt, welches von beiden |
| Jeder andere 4xx | Dein eigener Code hat ihn gewählt, etwa indem er die Antwort eines Dienstes weitergibt | Die Antwort |
Die Bedeutungen stammen aus der Statuscode-Referenz von Supabase und ihrer Seite zu den Limits. Für einen 404 reicht diese Tabelle: Vergleich den Namen der Funktion in deiner App mit der Liste im Dashboard, und prüf, ob sie in dem Projekt deployt ist, mit dem deine App spricht. Die anderen Zeilen haben unten je einen eigenen Abschnitt.
500: Deine Funktion lief und ist gescheitert
Irgendetwas in deinem Code hat einen Fehler geworfen, und der Tab Logs hat die Zeile.
Die Seite von Supabase zu diesem Fehler
teilt ihn in zwei Fälle. Lautet die Antwort genau Internal Server Error, ist
dein Code abgestürzt, und das Log zeigt die Art des Fehlers und die Zeile, in der
er passiert ist, etwa TypeError: Cannot read properties of undefined. Lautet
die Antwort anders, hat dein Code diese Meldung selbst geschrieben und die 500
absichtlich geliefert, also such in der Funktion nach dem Status, den sie
geschickt hat.
Eine Ursache lohnt es vor allen anderen auszuschließen, wenn eine Funktion OpenAI, Stripe oder irgendeine bezahlte API aufruft: ein Secret, das auf deinem Rechner existiert und in deinem Live-Projekt nicht.
In der Funktion wird ein Secret mit Deno.env.get('OPENAI_API_KEY') gelesen.
Ist kein Secret dieses Namens gesetzt, liefert das laut Doku von Deno
undefined, statt zu
scheitern. Die Funktion läuft also weiter, baut eine Anfrage mit dem Wort
undefined an der Stelle des Schlüssels und schickt sie ab. OpenAI antwortet
mit einem eigenen 401, "Incorrect API key provided", und deine Funktion gibt das
entweder weiter oder stürzt beim Lesen einer Antwort ab, mit der sie nicht
gerechnet hat. Im Bild mit dem Umschlag: Das Büro hat deinen Brief geöffnet, ist
zu der Schublade gegangen, in der der Schlüssel liegen sollte, hat sie leer
vorgefunden und eine Notiz über einen falschen Schlüssel zurückgeschickt.
Die Reparatur liegt auf der Seite Edge Function Secrets deines
Supabase-Projekts. Laut
Secrets-Leitfaden von Supabase
liest eine Funktion ein neues Secret sofort, du musst also nicht neu deployen,
und kein Secret-Name darf mit SUPABASE_ beginnen, weil dieses Präfix für die
Werte reserviert ist, die Supabase für dich setzt. Ein Secret, das du für die
lokale Entwicklung setzt, bleibt auf deinem Rechner; das Live-Projekt hat seine
eigene Liste.
401: Abgewiesen, bevor dein Code lief
Lautet die Antwort Invalid JWT oder Missing authorization header, hat deine
Funktion die Anfrage nie gesehen. Die Plattform von Supabase hat sie an der Tür
abgewiesen.
Diese Tür ist eine Prüfung namens verify_jwt, standardmäßig an, die den Header
Authorization jeder Anfrage liest, bevor dein Code läuft. Die
Seite von Supabase zum 401
nennt drei Arten, an ihr zu scheitern:
- Der Schlüssel passt nicht zu den Schlüsseln deines Projekts. Zum Beispiel ein Schlüssel aus einem anderen Projekt oder ein alter, der nach einer Rotation noch irgendwo eingetragen ist.
- Es gibt überhaupt keinen Header
Authorization. supabase-js fügt einen für dich hinzu. Ein einfachesfetchan die Adresse der Funktion oder ein Webhook von einem anderen Dienst tut das nicht. - Dein Projekt nutzt die neueren Signaturschlüssel von Supabase. Die eingebaute Prüfung versteht nur das ältere Format, und die Lösung von Supabase ist, die Prüfung für diese Funktion abzuschalten.
Dieser letzte Punkt reicht über den einen Fall hinaus. Supabase empfiehlt inzwischen, die eingebaute Prüfung abzuschalten und die Authentifizierung direkt im Code deiner Funktion zu erledigen. Die Empfehlung hat zwei Hälften, und die zweite ist die, die unter den Tisch fällt.
Kam der 401 mit einer Meldung, die dein eigener Code geschrieben hat, hat die
Plattform die Anfrage durchgelassen, und deine Funktion hat sie abgewiesen oder
einen 401 des aufgerufenen Dienstes weitergegeben, oft das leere Secret aus dem
Abschnitt zur 500. Das ist außerdem eine andere Prüfung als die hinter
PGRST301 bei deinen Datenbankabfragen, die
eine eigene Seite hat. Steht im Log der Funktion
invalid claim: missing sub claim, hat sie Supabase Auth gefragt, wer da
aufruft, und statt eines angemeldeten Nutzers den Schlüssel deines Projekts
bekommen; der Beitrag zu dieser Meldung
verfolgt das.
546 und 504: keine Ressourcen oder keine Zeit mehr
Supabase stoppt eine Funktion, die zu viel braucht, und die beiden Zahlen sagen, welches Limit sie getroffen hat.
546 ist Rechenleistung. Die
Seite zu den Limits gibt
einer Funktion 256 MB Speicher und jeder Anfrage 2 Sekunden CPU-Zeit. Geht eines
davon darüber, lautet die Antwort
Function failed due to not having enough compute resources (please check logs),
mit dem Code WORKER_RESOURCE_LIMIT (ältere Logs sagen WORKER_LIMIT). Der Tab
Logs sagt CPU Time exceeded oder Memory limit exceeded. Die
Seite von Supabase zum 546
nennt Bildverarbeitung, KI-Embeddings und Web-Scraping als Beispiele, und ihre
Antwort ist, pro Anfrage weniger zu tun oder den schweren Teil an einen anderen
Dienst abzugeben.
504 ist Zeit. Eine Funktion, die innerhalb von 150 Sekunden keine Antwort schickt, bekommt einen 504. Das CPU-Limit zählt nur die Zeit, in der dein Code tatsächlich rechnet: Laut der Seite zu den Limits zählt asynchrone Ein- und Ausgabe nicht dazu, und genau das ist Warten auf das Netz. Eine Funktion, die auf ein langsames KI-Modell wartet, verbraucht also keine CPU. Sie lässt die Uhr von 150 Sekunden ablaufen, und wenn die abgelaufen ist, bekommst du einen 504 und keinen 546.
Zwei Lösungen, die es schlimmer machen
Zwei Änderungen lassen diese Meldung in wenigen Minuten verschwinden, und beide lassen dich schlechter dastehen als der Fehler.
Die API stattdessen aus der Seite aufrufen. Am schnellsten hört eine
fehlschlagende Funktion auf zu fehlschlagen, wenn man sie nicht mehr benutzt:
den Schlüssel in eine Variable mit VITE_ legen und OpenAI direkt aus deinem
Frontend aufrufen. Das klappt beim ersten Versuch. Es legt den Schlüssel aber
auch in die Datei, die jeder Besucher herunterlädt, denn dieses Präfix ist
eine Anweisung, den Wert in dein Bundle zu kompilieren,
und dort kann ihn jeder lesen. Unter den Live-Apps, die wir gescannt haben,
tauchte im JavaScript von 33 ein OpenAI-Schlüssel auf, und
was ein Fremder damit anfangen kann,
steht in einem eigenen Beitrag.
Die Prüfung abschalten und dabei stehen bleiben. Einen 401 zu beseitigen,
indem man verify_jwt abschaltet, ist die erste Hälfte des eigenen Rats von
Supabase. Der
Konfigurationsleitfaden von Supabase
sagt klar, was das allein bewirkt: Dann kann jeder deine Edge Function ohne
gültiges JWT aufrufen. Die Adresse deiner Funktion steht in deinem Frontend, für
jeden lesbar. Hält die Funktion einen OpenAI-Schlüssel, kann ein Fremder ihn
jetzt über dich ausgeben. Nutzt sie den geheimen Schlüssel des Projekts, den
Supabase in die Umgebung jeder Funktion legt, läuft die Anfrage eines Fremden mit
einem Schlüssel, der an jeder Row-Level-Security-Regel vorbeikommt, die du
geschrieben hast.
Die zweite Hälfte, eine Prüfung im Code der Funktion, wer da aufruft, ist ein paar Zeilen lang. Ein Secret in eine Edge Function verschieben enthält sie und erklärt, warum selbst die Standardprüfung mehr Aufrufer durchlässt, als ihr Name vermuten lässt.
Den ersten Fehler sieht man von außen, und so haben wir ihn gezählt. Unser kostenloser Scan liest das JavaScript deiner Live-App so, wie ein Fremder es lesen würde, und nennt jeden Schlüssel, den er findet, samt der Angabe, ob er dorthin gehört. Das dauert etwa 20 Sekunden und braucht kein Konto: scanne deine App.
Damit der Schlüssel bleibt, wo du ihn hingelegt hast
Die Lösung von heute kann der Prompt von nächster Woche rückgängig machen. Ein Builder, der ein Feature wieder zum Laufen bringen soll, darf die Abkürzung von oben nehmen, und deine App läuft weiter, während er es tut.
Reeve Monitor prüft jede Stunde, damit du es nicht musst:
- alle neun Checks jede Stunde, für bis zu drei Apps
- eine E-Mail an dem Tag, an dem ein erneuter Scan deine App schlechter benotet als der vorige; ein geheimer Schlüssel in deinem JavaScript deckelt die Note allein schon bei D
- ob die App erreichbar ist, alle 60 Sekunden
- ein monatlicher Bericht darüber, was gesehen wurde
Eine Funktion, die mit deinem geheimen Schlüssel läuft, kommt an jeder Row-Level-Security-Regel vorbei. Reeve Care hält eine Kopie deiner Supabase-Datenbank dort, wo nichts in deinem Projekt sie erreicht.
- eine verschlüsselte Kopie nach Zeitplan, auf Reeves eigenem Speicher in der EU, außerhalb deines Supabase-Kontos
- jede Kopie wird geprüft, bevor sie zählt, durch Zählen der Zeilen in jeder Tabelle
- eine Wiederherstellung per Klick, die sichert, was jetzt da ist, bevor sie es ersetzt
- deine hochgeladenen Dateien ebenfalls, sobald du eine Storage-Zugangsberechtigung verbindest
- alles, was Monitor tut
Was jetzt zu tun ist
Was zu tun ist
- Lös den Fehler einmal aus und merk dir die Uhrzeit.
- Öffne die Anfrage im Tab Network deines Browsers und lies Status und Antwort, bevor du irgendetwas änderst.
- Lies die Tabs Invocations und Logs der Funktion im Supabase-Dashboard zu dieser Minute.
- Bei einer 500 aus einer Funktion, die eine bezahlte API aufruft: Prüf, ob das Secret auf der Seite Edge Function Secrets des Live-Projekts gesetzt ist. Danach ist kein neues Deploy nötig.
- Bei einem 401 von der Plattform: Korrigier den Schlüssel oder schalte die eingebaute Prüfung ab, und füg in derselben Änderung eine Prüfung in der Funktion hinzu, wer da aufruft.
- Beseitige diesen Fehler nie, indem du den Schlüssel in eine Variable mit
VITE_verschiebst.
Die Prüfung, wer da aufruft, ist der Schritt, den man am leichtesten auslässt, und die Anleitung zu Secrets in Edge Functions behandelt ihn vollständig. Die 10-Minuten-Sicherheitscheckliste deckt ab, was in einer frisch gestarteten App sonst gern offen bleibt.
FAQ
Was bedeutet "Edge Function returned a non-2xx status code"?
Deine Supabase Edge Function hat mit einem Fehlerstatus geantwortet, also mit irgendeiner Zahl außerhalb der 200er, und supabase-js hat aus dieser Antwort einen festen Satz gemacht. Der Satz ist derselbe für einen Absturz, einen fehlenden Schlüssel, eine Anfrage, die abgewiesen wurde, bevor dein Code lief, und eine Zeitüberschreitung. Die Antwort selbst, die sagt, was davon es war, hängt noch am Fehler, und die Funktion führt ein eigenes Log.
Wie sehe ich den echten Fehler dahinter?
Öffne deine Live-App, drück F12, wähl den Tab Network und wiederhole, was fehlgeschlagen ist. Klick die Anfrage an, deren Adresse /functions/v1/ enthält, und lies ihren Status und ihre Antwort: Das ist, was die Funktion tatsächlich geschickt hat. Öffne dann Edge Functions in deinem Supabase-Dashboard, wähl die Funktion und lies zur selben Zeit die Tabs Invocations und Logs. Im Code liest der Leitfaden von Supabase dieselbe Antwort mit await error.context.json().
Warum läuft meine Edge Function lokal und scheitert nach dem Deploy?
Prüf zuerst, ob ein Secret auf deinem Rechner existiert und in deinem Live-Projekt nicht. Die lokale Umgebung liest ihre eigene Umgebungsdatei, die deployte Funktion liest die Secrets, die im Projekt gesetzt sind. Ein fehlendes Secret kommt als undefined zurück statt als Fehler, also läuft die Funktion weiter und scheitert später, meist wenn der aufgerufene Dienst einen leeren Schlüssel ablehnt. Setz es auf der Seite Edge Function Secrets; laut Supabase wird ein neues Secret sofort gelesen, ohne neues Deploy.
Soll ich die JWT-Prüfung abschalten, um einen 401 loszuwerden?
Supabase empfiehlt inzwischen, die eingebaute Prüfung abzuschalten und den Aufrufer stattdessen in deiner Funktion zu prüfen. Beide Hälften zählen. Allein abgeschaltet hält die Prüfung nichts mehr auf, und die Doku von Supabase warnt selbst, dass dann jeder deine Funktion ohne gültiges JWT aufrufen kann. Hält die Funktion einen OpenAI-Schlüssel oder den geheimen Schlüssel des Projekts, ist das jeder im Internet, der dein Kontingent verbraucht oder an deinen Row-Level-Security-Regeln vorbeikommt.
Was bedeutet Fehler 546 bei einer Supabase Edge Function?
Der Funktion ist die Rechenleistung ausgegangen: mehr als 2 Sekunden CPU-Zeit oder mehr als ihr Speicher bei einer Anfrage. Die Antwort lautet "Function failed due to not having enough compute resources (please check logs)", und der Tab Logs sagt, welches Limit es war, CPU Time exceeded oder Memory limit exceeded. Das Warten auf eine langsame API zählt nicht als CPU-Zeit; eine Funktion, die zu lange wartet, bekommt stattdessen einen 504.