Situación actual: PHP2Shell. Dos vulnerabilidades críticas de PHP (CVSS 9.2) permiten ejecutar código malicioso sin autenticación. Parches desde el 21/10/2026.Qué hacer ahora →

Errores y excepciones en PHP

PHP conoce los mensajes de error clásicos y las excepciones. Esta página explica la jerarquía Throwable, el tratamiento limpio de errores, los ajustes seguros en producción y la medida de PHP2Shell sobre fatal_error_backtraces.

Actualizado 6 min de lecturaConocimiento de PHP

En resumen: PHP comunica los problemas por dos vías: como errores clásicos, como advertencias y avisos, que se controlan con error_reporting, y como excepciones, que se capturan con try y catch. Desde PHP 7, muchos errores internos también lanzan un objeto de la clase Error; junto con Exception, implementa la interfaz Throwable. En producción rige lo siguiente: registrar los errores en el log y no mostrarlos nunca en el navegador. En PHP 8.5, a causa de PHP2Shell, fatal_error_backtraces=0 forma además parte de las medidas provisionales.

Dos mundos: errores clásicos y excepciones

PHP viene de una época en la que los errores se emitían sobre todo como mensajes: avisos (E_NOTICE), advertencias (E_WARNING), funciones obsoletas (E_DEPRECATED) y errores fatales (E_ERROR). Muchos de estos mensajes dejan que el script siga ejecutándose, a menudo con datos incorrectos o vacíos.

Las excepciones funcionan de otro modo. Interrumpen el flujo normal y saltan al siguiente bloque catch que encaje. Si no se encuentra ninguno, el script termina. Para quienes toman decisiones, la diferencia es importante: una advertencia silenciosa puede hacer que un pedido se guarde de forma incompleta sin que nadie lo note. Una excepción hace visible el problema y permite tratarlo.

Una utilidad infravalorada de los mensajes clásicos son los avisos de tipo E_DEPRECATED. Anuncian que un recurso del lenguaje desaparecerá en una versión futura de PHP. Quien los recoge en el log y los evalúa con regularidad sabe, antes de una actualización, qué puntos hay que adaptar, y no tiene que probar durante mucho tiempo cuando surge una vulnerabilidad urgente.

La jerarquía Throwable

Todo lo que se puede lanzar con throw y capturar con catch implementa Throwable. De ahí cuelgan dos ramas:

Clase Finalidad Ejemplos de subclases
Exception errores previsibles de la aplicación y de las extensiones ErrorException, PDOException, clases propias
Error errores internos de PHP TypeError, ValueError, ArgumentCountError, ArithmeticError, DivisionByZeroError, UnhandledMatchError

Las clases de error propias derivan de Exception, no de Error. Desde PHP 8.0, la mayoría de las funciones internas lanzan un TypeError o un ValueError ante parámetros no válidos, en lugar de emitir solo una advertencia. strlen([]) es uno de esos casos.

try, catch y finally en la práctica

<?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) {
        // desde PHP 8.0 la variable en el catch es opcional
        $log->error('Zahlungsdienst nicht erreichbar', ['bestellung' => $b->id()]);
        return false;
    } finally {
        $dienst->verbindungSchliessen();
    }
}

Varias clases se pueden agrupar en un catch con |. El bloque finally se ejecuta siempre, haya ocurrido un error o no. Desde PHP 8.0, además, throw es una expresión y puede aparecer, por ejemplo, tras ??: $id = $_GET['id'] ?? throw new InvalidArgumentException('id fehlt');.

Handlers globales: set_error_handler y set_exception_handler

Dos funciones interceptan lo que de otro modo se escaparía. set_exception_handler() registra una función para los Throwable no capturados. Según el manual, el script termina después de llamarla; sirve, por tanto, para registrar en el log y mostrar una página de error neutra, no para seguir trabajando.

Ha dado buen resultado un handler que escribe en el log el error completo con la hora y un identificador aleatorio, y que al visitante solo le muestra una página neutra con ese identificador. El soporte puede entonces localizar el caso en el log sin que salgan al exterior detalles internos.

set_error_handler() captura los errores clásicos. El manual muestra cómo convertirlos en una ErrorException, de modo que las advertencias se traten como excepciones:

<?php
set_error_handler(function (int $nr, string $text, string $datei, int $zeile): bool {
    if (!(error_reporting() & $nr)) {
        return false; // excluido por error_reporting
    }
    if ($nr === E_DEPRECATED || $nr === E_USER_DEPRECATED) {
        return false; // no escalar los avisos de funciones obsoletas
    }
    throw new ErrorException($text, 0, $nr, $datei, $zeile);
});

Los errores fatales, como un desbordamiento de memoria, no se pueden capturar por esta vía. Qué ocurre en el interior durante un desbordamiento lo explica la página memory_limit y Out-of-Memory.

Configurar la salida de errores y el registro

Los ajustes más importantes con sus valores por defecto según el manual:

Ajuste Valor por defecto Recomendación en producción
error_reporting E_ALL desde PHP 8.0.0 E_ALL
display_errors 1 Off
display_startup_errors 1 desde PHP 8.0.0 Off
log_errors 0 On
error_log vacío (log del servidor web o de la SAPI) archivo propio fuera del webroot
fatal_error_backtraces 1, nuevo en PHP 8.5.0 provisionalmente 0 (PHP2Shell)

Desde PHP 8.2.0, error_log_mode fija los permisos del archivo de log, por defecto 0644. Si los logs contienen datos personales o rutas, conviene un valor más estricto para que otros usuarios del servidor no puedan leerlos. Con ignore_repeated_errors se pueden agrupar mensajes idénticos del mismo archivo y línea, para que un bucle de errores no inunde el log.

El php.ini-production incluido ya pone display_errors en Off. Según el manual, ini_set('display_errors', ...) no actúa en tiempo de ejecución sobre los errores fatales, y los errores de sintaxis ocurren antes de que se ejecute cualquier ini_set(). Por eso estos valores pertenecen a la configuración del servidor. Los antecedentes figuran en el artículo Desactivar la salida de errores en producción; cómo llevar los logs sin filtrar datos, en el artículo Registrar logs con seguridad.

Tratamiento de errores y PHP2Shell

Para PHP 8.5, php.net indica como una de las medidas provisionales frente a PHP2Shell poner fatal_error_backtraces=0 y reiniciar PHP después. Las demás medidas afectan a memory_limit, post_max_size y session.upload_progress.enabled. Ninguna de ellas sustituye a la actualización a PHP 8.5.12, 8.4.27, 8.3.36 o 8.2.35.

; solo PHP 8.5, en php.ini o en el pool de FPM; después, reiniciar todos los workers
fatal_error_backtraces = 0

Además, tras PHP2Shell los logs de errores son una prueba importante. Si a partir del 20/10/2026 se acumulan errores de memoria o workers interrumpidos, puede indicar intentos de ataque, aunque también puede tener causas inofensivas. Conserve los logs y evalúelos junto con los logs de acceso, como se describe en la guía Analizar los logs del servidor web tras un ataque.

¿Cómo ayuda PHP SOS en concreto?

  • Revisamos los ajustes efectivos de salida de errores y de registro de cada sitio web y los dejamos aptos para producción.
  • Aplicamos las medidas provisionales de PHP2Shell adecuadas a la versión de PHP y reiniciamos todos los workers.
  • Aseguramos los logs de errores y de acceso desde el 20/10/2026 y los analizamos en busca de anomalías.
  • Como parte de la monitorización de seguridad avisamos de acumulaciones inusuales de errores, en lugar de dejar que se pierdan en el log.

Preguntas frecuentes

¿Qué diferencia hay entre Error y Exception?

Ambas implementan la interfaz Throwable. Exception está pensada para errores que una aplicación espera y gestiona, como una conexión fallida con la base de datos. Error representa los errores internos de PHP, como un TypeError o una división por cero.

¿Debe estar activado display_errors en producción?

No. Los mensajes de error en el navegador revelan rutas, consultas y, a veces, credenciales. En producción, display_errors debe estar en Off y log_errors en On, para que los errores acaben en el log y no ante el visitante.

¿Se puede capturar un desbordamiento de memoria con try/catch?

No. Si un script supera memory_limit, PHP lo termina con un error fatal que no se puede capturar como excepción. Solo aparece en el log de errores, siempre que log_errors esté activo.

¿Qué es fatal_error_backtraces?

Un ajuste que existe desde PHP 8.5.0 y que añade un backtrace a los errores fatales. Está activo por defecto. Como medida provisional frente a PHP2Shell, php.net indica para PHP 8.5 ponerlo a 0 y reiniciar PHP después.

Fuentes

Contacto inmediato

Cada hora cuenta. Escríbanos ahora.

Describa brevemente el sistema, la versión de PHP y lo que ha observado. Nos pondremos en contacto, aclararemos la situación y trabajaremos en remoto hasta que el incidente quede cerrado y documentado.

Ayuda inmediata