Kurz gesagt: PHP meldet Probleme auf zwei Wegen: als klassische Fehler wie Warnungen und Hinweise, die über error_reporting gesteuert werden, und als Exceptions, die sich mit try und catch abfangen lassen. Seit PHP 7 werfen auch viele interne Fehler ein Objekt der Klasse Error; gemeinsam mit Exception implementiert es das Interface Throwable. In der Produktion gilt: Fehler protokollieren, nie im Browser anzeigen. Auf PHP 8.5 gehört wegen PHP2Shell außerdem fatal_error_backtraces=0 zu den Übergangsmaßnahmen.
Zwei Welten: klassische Fehler und Exceptions
PHP stammt aus einer Zeit, in der Fehler vor allem als Meldung ausgegeben wurden: Hinweise (E_NOTICE), Warnungen (E_WARNING), veraltete Funktionen (E_DEPRECATED) und fatale Fehler (E_ERROR). Viele dieser Meldungen lassen das Skript weiterlaufen, oft mit falschen oder leeren Daten.
Exceptions arbeiten anders. Sie unterbrechen den normalen Ablauf und springen zum nächsten passenden catch-Block. Wird keiner gefunden, endet das Skript. Für Entscheider ist der Unterschied wichtig: Eine stille Warnung kann dazu führen, dass eine Bestellung unvollständig gespeichert wird, ohne dass es jemand merkt. Eine Exception macht das Problem sichtbar und behandelbar.
Ein unterschätzter Nutzen der klassischen Meldungen sind Hinweise vom Typ E_DEPRECATED. Sie kündigen an, dass ein Sprachmittel in einer künftigen PHP-Version wegfällt. Wer sie im Log sammelt und regelmäßig auswertet, weiß vor einem Upgrade, welche Stellen angepasst werden müssen, und muss bei einer dringenden Sicherheitslücke nicht erst lange testen.
Die Throwable-Hierarchie
Alles, was sich mit throw werfen und mit catch fangen lässt, implementiert Throwable. Darunter gibt es zwei Äste:
| Klasse | Zweck | Beispiele für Unterklassen |
|---|---|---|
Exception |
erwartbare Fehler der Anwendung und von Erweiterungen | ErrorException, PDOException, eigene Klassen |
Error |
interne Fehler von PHP | TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError |
Eigene Fehlerklassen leiten von Exception ab, nicht von Error. Seit PHP 8.0 werfen die meisten internen Funktionen bei ungültigen Parametern einen TypeError oder ValueError, statt nur eine Warnung auszugeben. strlen([]) ist ein solcher Fall.
try, catch und finally in der Praxis
<?php
declare(strict_types=1);
final class ZahlungFehlgeschlagen extends RuntimeException {}
function bezahlen(Bestellung $b, Zahlungsdienst $dienst, LoggerInterface $log): bool
{
try {
$dienst->belasten($b->betrag(), $b->referenz());
return true;
} catch (ZahlungFehlgeschlagen $e) {
$log->warning('Zahlung abgelehnt', ['bestellung' => $b->id(), 'exception' => $e]);
return false;
} catch (TimeoutException | VerbindungsFehler) {
// seit PHP 8.0 ist die Variable im catch optional
$log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
return false;
} finally {
$dienst->verbindungSchliessen();
}
}
Mehrere Klassen lassen sich mit | in einem catch zusammenfassen. Der finally-Block läuft immer, ob ein Fehler aufgetreten ist oder nicht. Seit PHP 8.0 ist throw außerdem ein Ausdruck und kann etwa hinter ?? stehen: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.
Globale Handler: set_error_handler und set_exception_handler
Zwei Funktionen fangen ab, was sonst durchrutschen würde. set_exception_handler() registriert eine Funktion für nicht abgefangene Throwables. Laut Handbuch endet das Skript, nachdem sie aufgerufen wurde; sie eignet sich also für Protokollierung und eine neutrale Fehlerseite, nicht zum Weiterarbeiten.
Bewährt hat sich ein Handler, der den vollständigen Fehler mit Zeitpunkt und einer zufälligen Kennung ins Log schreibt und dem Besucher nur eine neutrale Seite mit dieser Kennung zeigt. Der Support kann den Fall dann im Log wiederfinden, ohne dass interne Details nach außen dringen.
set_error_handler() fängt klassische Fehler ab. Das Handbuch zeigt, wie man sie in eine ErrorException umwandelt, damit Warnungen wie Exceptions behandelt werden:
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
if (!(error_reporting() & $nr)) {
return false; // von error_reporting ausgeschlossen
}
if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
return false; // Hinweise auf Veraltetes nicht eskalieren
}
throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
Fatale Fehler wie ein Speicherüberlauf lassen sich auf diesem Weg nicht abfangen. Was bei einem Überlauf im Inneren passiert, erklärt die Seite memory_limit und Out-of-Memory.
Fehlerausgabe und Protokollierung konfigurieren
Die wichtigsten Einstellungen mit ihren Standardwerten laut Handbuch:
| Einstellung | Standard | Empfehlung Produktion |
|---|---|---|
error_reporting |
E_ALL seit PHP 8.0.0 |
E_ALL |
display_errors |
1 |
Off |
display_startup_errors |
1 seit PHP 8.0.0 |
Off |
log_errors |
0 |
On |
error_log |
leer (Log des Webservers bzw. der SAPI) | eigene Datei außerhalb des Webroots |
fatal_error_backtraces |
1, neu in PHP 8.5.0 |
vorübergehend 0 (PHP2Shell) |
Seit PHP 8.2.0 legt error_log_mode die Dateirechte der Logdatei fest, standardmäßig 0644. Enthalten die Logs personenbezogene Daten oder Pfade, ist ein strengerer Wert sinnvoll, damit andere Benutzer auf dem Server sie nicht lesen können. Mit ignore_repeated_errors lassen sich identische Meldungen aus derselben Datei und Zeile zusammenfassen, damit eine Fehlerschleife das Log nicht überflutet.
Die mitgelieferte php.ini-production setzt display_errors bereits auf Off. Laut Handbuch wirkt ini_set('display_errors', ...) zur Laufzeit nicht auf fatale Fehler, und Syntaxfehler treten auf, bevor ein ini_set() überhaupt läuft. Diese Werte gehören deshalb in die Serverkonfiguration. Hintergründe stehen im Beitrag Fehlerausgaben in der Produktion abschalten; wie Logs ohne Datenleck geführt werden, im Beitrag Sicher loggen.
Fehlerbehandlung und PHP2Shell
Für PHP 8.5 nennt php.net als eine der Übergangsmaßnahmen gegen PHP2Shell, fatal_error_backtraces=0 zu setzen und PHP danach neu zu starten. Die übrigen Maßnahmen betreffen memory_limit, post_max_size und session.upload_progress.enabled. Keine davon ersetzt das Update auf PHP 8.5.12, 8.4.27, 8.3.36 oder 8.2.35.
; nur PHP 8.5, in php.ini oder FPM-Pool, danach alle Worker neu starten
fatal_error_backtraces = 0
Fehlerlogs sind nach PHP2Shell außerdem ein wichtiges Beweismittel. Häufen sich ab dem 20.10.2026 Speicherfehler oder abgebrochene Worker, kann das auf Angriffsversuche hindeuten, es kann aber auch harmlose Ursachen haben. Bewahren Sie die Logs auf und werten Sie sie gemeinsam mit den Zugriffslogs aus, wie im Ratgeber Webserver-Logs nach einem Angriff auswerten beschrieben.
Wie hilft PHP SOS konkret?
- Wir prüfen die wirksamen Einstellungen für Fehlerausgabe und Protokollierung je Website und stellen sie produktionstauglich ein.
- Wir setzen die PHP2Shell-Übergangsmaßnahmen passend zur PHP-Version und starten alle Worker neu.
- Wir sichern Fehler- und Zugriffslogs ab dem 20.10.2026 und werten sie auf Auffälligkeiten aus.
- Im Rahmen des Security-Monitorings melden wir ungewöhnliche Fehlerhäufungen, statt sie im Log versanden zu lassen.
Häufige Fragen
Was ist der Unterschied zwischen Error und Exception?
Beide implementieren das Interface Throwable. Exception ist für Fehler gedacht, die eine Anwendung erwartet und behandelt, etwa eine fehlgeschlagene Datenbankverbindung. Error steht für interne Fehler von PHP wie einen TypeError oder eine Division durch null.
Sollte display_errors in der Produktion an sein?
Nein. Fehlermeldungen im Browser verraten Pfade, Abfragen und manchmal Zugangsdaten. In der Produktion gehört display_errors auf Off und log_errors auf On, damit Fehler im Log landen statt beim Besucher.
Kann man einen Speicherüberlauf mit try/catch abfangen?
Nein. Überschreitet ein Skript memory_limit, beendet PHP es mit einem fatalen Fehler, der sich nicht als Exception abfangen lässt. Er erscheint nur im Fehlerlog, sofern log_errors aktiv ist.
Was ist fatal_error_backtraces?
Eine Einstellung, die es seit PHP 8.5.0 gibt und die fatalen Fehlern einen Backtrace beifügt. Sie ist standardmäßig aktiv. Als Übergangsmaßnahme gegen PHP2Shell nennt php.net für PHP 8.5, sie auf 0 zu setzen und PHP danach neu zu starten.