# Errors and Exceptions in PHP

> PHP errors and exceptions: Throwable, Error and Exception, try/catch/finally, global handlers and safe settings for error display and logging in production.

Source: https://phpsos.de/en/php/fehler-und-exceptions/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** PHP reports problems in two ways: as classic errors such as warnings and notices, which are controlled through `error_reporting`, and as exceptions, which can be caught with `try` and `catch`. Since PHP 7, many internal errors also throw an object of the `Error` class; together with `Exception` it implements the `Throwable` interface. In production the rule is: log errors, never display them in the browser. On PHP 8.5, `fatal_error_backtraces=0` is also one of the interim measures because of PHP2Shell.

## Two worlds: classic errors and exceptions

PHP dates from a time when errors were mainly output as messages: notices (`E_NOTICE`), warnings (`E_WARNING`), deprecated features (`E_DEPRECATED`) and fatal errors (`E_ERROR`). Many of these messages let the script keep running, often with wrong or empty data.

Exceptions work differently. They interrupt the normal flow and jump to the next matching `catch` block. If none is found, the script ends. For decision-makers the difference matters: a silent warning can cause an order to be saved incompletely without anyone noticing. An exception makes the problem visible and manageable.

An underrated benefit of the classic messages is notices of type `E_DEPRECATED`. They announce that a language feature will be removed in a future PHP version. Anyone who collects them in the log and evaluates them regularly knows before an upgrade which places need adjusting, and does not have to test at length first when an urgent security flaw appears.

## The Throwable hierarchy

Everything that can be thrown with `throw` and caught with `catch` implements `Throwable`. Beneath it there are two branches:

| Class | Purpose | Examples of subclasses |
|---|---|---|
| `Exception` | foreseeable errors of the application and of extensions | `ErrorException`, `PDOException`, custom classes |
| `Error` | internal PHP errors | `TypeError`, `ValueError`, `ArgumentCountError`, `ArithmeticError`, `DivisionByZeroError`, `UnhandledMatchError` |

Custom error classes derive from `Exception`, not from `Error`. Since PHP 8.0, most internal functions throw a `TypeError` or `ValueError` for invalid parameters instead of merely issuing a warning. `strlen([])` is one such case.

## try, catch and finally in practice

```php
<?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) {
        // since PHP 8.0 the variable in catch is optional
        $log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
        return false;
    } finally {
        $dienst->verbindungSchliessen();
    }
}
```

Several classes can be combined in one `catch` with `|`. The `finally` block always runs, whether an error occurred or not. Since PHP 8.0, `throw` is also an expression and can appear, for example, after `??`: `$id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');`.

## Global handlers: set_error_handler and set_exception_handler

Two functions catch what would otherwise slip through. `set_exception_handler()` registers a function for uncaught Throwables. According to the manual, the script ends after it has been called; it is therefore suited to logging and a neutral error page, not to carrying on.

A handler that writes the complete error to the log with a timestamp and a random identifier, and shows the visitor only a neutral page with that identifier, has proven its worth. Support can then find the case in the log without internal details leaking to the outside.

`set_error_handler()` catches classic errors. The manual shows how to convert them into an `ErrorException` so that warnings are handled like exceptions:

```php
<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
    if (!(error_reporting() & $nr)) {
        return false; // excluded by error_reporting
    }
    if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
        return false; // do not escalate deprecation notices
    }
    throw new ErrorException($text, 0, $nr, $datei, $zeile);
});
```

Fatal errors such as a memory overflow cannot be caught this way. What happens internally during an overflow is explained on the page [memory_limit and out-of-memory](https://phpsos.de/en/php/out-of-memory/).

## Configuring error display and logging

The most important settings with their default values according to the manual:

| Setting | Default | Production recommendation |
|---|---|---|
| `error_reporting` | `E_ALL` since PHP 8.0.0 | `E_ALL` |
| `display_errors` | `1` | `Off` |
| `display_startup_errors` | `1` since PHP 8.0.0 | `Off` |
| `log_errors` | `0` | `On` |
| `error_log` | empty (log of the web server or SAPI) | a dedicated file outside the web root |
| `fatal_error_backtraces` | `1`, new in PHP 8.5.0 | temporarily `0` (PHP2Shell) |

Since PHP 8.2.0, `error_log_mode` sets the file permissions of the log file, `0644` by default. If the logs contain personal data or paths, a stricter value makes sense so that other users on the server cannot read them. With `ignore_repeated_errors`, identical messages from the same file and line can be merged, so that an error loop does not flood the log.

The bundled `php.ini-production` already sets `display_errors` to `Off`. According to the manual, `ini_set('display_errors', ...)` at runtime has no effect on fatal errors, and syntax errors occur before any `ini_set()` runs at all. These values therefore belong in the server configuration. Background is given in the article [Switching off error output in production](https://phpsos.de/en/blog/display-errors-produktion/); how to keep logs without a data leak is covered in the article [Logging safely](https://phpsos.de/en/blog/sicher-loggen-php/).

## Error handling and PHP2Shell

For PHP 8.5, php.net names setting `fatal_error_backtraces=0` and restarting PHP afterwards as one of the interim measures against PHP2Shell. The other measures concern `memory_limit`, `post_max_size` and `session.upload_progress.enabled`. None of them replaces the update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35.

```ini
; PHP 8.5 only, in php.ini or the FPM pool, then restart all workers
fatal_error_backtraces = 0
```

After PHP2Shell, error logs are also important evidence. If memory errors or aborted workers accumulate from 20 October 2026 onwards, this may point to attack attempts, but it can also have harmless causes. Keep the logs and evaluate them together with the access logs, as described in the guide [Analysing web server logs after an attack](https://phpsos.de/en/ratgeber/logfiles-auswerten/).

## How does PHP SOS help?

- We check the effective settings for error display and logging for each website and configure them for production use.
- We apply the PHP2Shell interim measures to match the PHP version and restart all workers.
- We secure error and access logs from 20 October 2026 onwards and evaluate them for anomalies.
- As part of [security monitoring](https://phpsos.de/en/leistungen/security-monitoring/), we report unusual clusters of errors instead of letting them fade away in the log.


## Frequently asked questions

**What is the difference between Error and Exception?**  
Both implement the Throwable interface. Exception is meant for errors that an application expects and handles, such as a failed database connection. Error stands for internal PHP errors such as a TypeError or a division by zero.

**Should display_errors be on in production?**  
No. Error messages in the browser reveal paths, queries and sometimes credentials. In production, display_errors belongs at Off and log_errors at On, so that errors end up in the log rather than with the visitor.

**Can a memory overflow be caught with try/catch?**  
No. If a script exceeds memory_limit, PHP terminates it with a fatal error that cannot be caught as an exception. It appears only in the error log, provided log_errors is active.

**What is fatal_error_backtraces?**  
A setting that has existed since PHP 8.5.0 and adds a backtrace to fatal errors. It is active by default. As an interim measure against PHP2Shell, php.net names setting it to 0 for PHP 8.5 and restarting PHP afterwards.

## Sources

- [PHP Manual: Error handling configuration](https://www.php.net/manual/de/errorfunc.configuration.php)
- [PHP Manual: The Error class](https://www.php.net/manual/de/class.error.php)
- [PHP Manual: ErrorException](https://www.php.net/manual/en/class.errorexception.php)
- [PHP Manual: set_exception_handler](https://www.php.net/manual/en/function.set-exception-handler.php)
- [php.net: Security announcements](https://www.php.net/)
