Añadir una página de error 500 en Drupal utilizando un Event Subscriber
A menudo nos encontramos con la página de error 500 cuando Drupal, los servicios u otros sitios no están disponibles. Cuando vemos una página de error 500 (o 501-504). En Drupal usamos Excepciones para verificar si se ejecutó algún código crítico. Si se produce un error, por ejemplo, en una solicitud HTTP a otro sitio, Drupal mostrará este error: "El sitio web encontró un error inesperado. Por favor, inténtelo de nuevo más tarde":

No es bueno tener una WSOD (pantalla blanca de la muerte) en tu sitio, así que vamos a mejorarla y mostrar en su lugar una página HTML con estilo.
Tengo una página 500.html con estilos en la raíz de mi sitio, por razones de rendimiento. Podemos usar una página de Drupal estilizada para el error 500, pero también reutilizo la misma página para los errores 503/504 en Apache/Nginx y será más fácil mantener esta página en un solo lugar como una única página HTML.

Ahora necesitamos añadir código en nuestro módulo personalizado DrupalBook Custom (drupalbook_custom). En drupalbook_custom.services.yml debemos añadir el Event Subscriber:
services:
drupalbook_custom.exception_subscriber:
class: Drupal\drupalbook_custom\EventSubscriber\SeoExceptionSubscriber
arguments: ['@config.factory']
tags:
- { name: event_subscriber, priority: -250 }
Este es el código para drupalbook_custom/src/EventSubscriber/SeoExceptionSubscriber:
<?php
namespace Drupal\drupalbook_custom\EventSubscriber;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Render\Markup;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Utility\Error;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Reemplaza Drupal\Core\EventSubscriber\FinalExceptionSubscriber para error 500.
*/
class SeoExceptionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Configuraciones para los ajustes.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected ConfigFactoryInterface $configFactory;
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* Maneja cualquier excepción no capturada y retorna una respuesta HTML personalizada.
*/
public function onException(ExceptionEvent $event): void {
// Muestra la traza de pila normal de Drupal cuando el sitio está en modo VERBOSE.
if ($this->isErrorLevelVerbose()) {
return;
}
$exception = $event->getThrowable();
// Mensaje básico (extender para modo verbose si es necesario).
$error = Error::decodeException($exception);
$message = new FormattableMarkup('@message', [
'@message' => $error['!message'] ?? $this->t('El sitio web encontró un error inesperado.'),
]);
$html = $this->buildHtml((string) $message);
$status = $exception instanceof HttpExceptionInterface
? $exception->getStatusCode()
: Response::HTTP_INTERNAL_SERVER_ERROR;
$response = new Response($html, $status, ['Content-Type' => 'text/html']);
// Conserva encabezados extra como Retry-After cuando estén presentes.
if ($exception instanceof HttpExceptionInterface) {
$response->headers->add($exception->getHeaders());
}
// Envía la respuesta y detiene otros subscribers (incluyendo el core).
$event->setResponse($response);
$event->stopPropagation();
}
/**
* Lee web/500.html e inserta un token {{ message }} si existe.
*/
protected function buildHtml(string $message): string {
$template = DRUPAL_ROOT . '/500.html';
if (is_readable($template)) {
$html = file_get_contents($template);
return str_replace('{{ message }}', Markup::create($message), $html);
}
// Respaldo seguro si falta la plantilla.
return '<html><head><title>500</title></head><body>'
. Markup::create($message)
. '</body></html>';
}
/**
* TRUE cuando el nivel de error está configurado en “Verbose”.
*
* Refleja \Drupal\Core\EventSubscriber\FinalExceptionSubscriber::isErrorLevelVerbose().
*/
protected function isErrorLevelVerbose(): bool {
return $this->configFactory
->get('system.logging')
->get('error_level') === ERROR_REPORTING_DISPLAY_VERBOSE;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array {
// Prioridad -250 → se ejecuta justo antes del FinalExceptionSubscriber del core (-256).
$events[KernelEvents::EXCEPTION][] = ['onException', -250];
return $events;
}
}
Esta clase subscriber, SeoExceptionSubscriber
, intercepta todas las excepciones no capturadas dentro de Drupal. Verifica si tu sitio Drupal está configurado en modo de reporte de errores verbose y, si es así, permite que Drupal muestre sus mensajes detallados estándar. Sin embargo, si el sitio no está en modo verbose (lo típico en producción), captura la excepción y prepara un mensaje de error amigable para el usuario.
Específicamente, lee tu archivo personalizado 500.html
ubicado en la raíz de tu instalación Drupal. Inserta dinámicamente el mensaje de error en el contenido HTML reemplazando el marcador {{ message }}
, asegurando que la página mostrada sea informativa y visualmente coherente.
Adicionalmente, el subscriber detiene explícitamente el manejador de errores por defecto de Drupal para que no procese más. Esto asegura que la página de error incorporada de Drupal no sobrescriba tu página HTML personalizada. Al definir el subscriber con una prioridad de -250
, se ejecuta justo antes del subscriber de error del core de Drupal, sobreescribiendo así el comportamiento predeterminado de Drupal.
Para tu entorno local puedes insertar ajustes para mostrar errores en lugar de la página de error 500.
settings.php:
$config['system.logging']['error_level'] = 'verbose';
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
A veces Drupal no es accesible, entonces será necesario establecer configuraciones adicionales para tu servidor web o la nube.
Agregar página de error 500 en Apache
Para servir tu página de error existente 500.html
desde la raíz del documento de tu sitio cada vez que ocurran errores HTTP 500–504, debes configurar Apache de la siguiente manera. Hay dos formas sencillas de hacerlo:
1. Usando la configuración del Virtual Host de Apache (recomendado)
Edita el archivo de configuración del virtual host de tu sitio (usualmente en /etc/apache2/sites-available/tu-sitio.conf
) y añade las siguientes directivas dentro del bloque <VirtualHost>
:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Luego, recarga Apache para aplicar los cambios:
sudo systemctl reload apache2
2. Usando el archivo .htaccess
Si prefieres usar el archivo .htaccess
(ubicado en la raíz del documento de tu sitio), simplemente inserta estas líneas:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Asegúrate de que el archivo 500.html
esté ubicado en el directorio raíz de tu sitio, accesible y legible por Apache. Después de aplicar estos ajustes, Apache mostrará tu página de error HTML personalizada de manera consistente para los errores 500 hasta 504.
Agregar página de error 500 en Nginx
Para configurar Nginx para mostrar tu página de error personalizada 500.html
ubicada en el directorio raíz de tu sitio para errores HTTP (500–504), actualiza la configuración del servidor de tu sitio Nginx así:
Edita el archivo de configuración de tu sitio en Nginx (usualmente en /etc/nginx/sites-available/tu-sitio.conf
) e inserta las siguientes directivas dentro de tu bloque server {}
:
error_page 500 501 502 503 504 /500.html;
location = /500.html {
root /var/www/html;
internal;
}
Asegúrate de que la ruta (/var/www/html
) apunte correctamente al directorio raíz de tu sitio que contiene el archivo 500.html
. Después de editar, recarga la configuración de Nginx para aplicar los cambios:
sudo nginx -s reload
Ahora Nginx mostrará tu página de error HTML personalizada de forma consistente para los estados de error HTTP 500 a 504.