Hinzufügen einer 500-Fehlerseite in Drupal mit Event Subscriber
Oft stoßen wir auf eine 500-Fehlerseite, wenn Drupal, Dienste oder andere Websites nicht verfügbar sind. Wenn wir eine 500- (oder 501–504-) Fehlerseite sehen. In Drupal verwenden wir Exceptions, um zu prüfen, ob kritischer Code ausgeführt wurde. Wenn wir einen Fehler bekommen, zum Beispiel bei einer HTTP-Anfrage an eine andere Website, zeigt Drupal diesen Fehler an: „Die Website hat einen unerwarteten Fehler festgestellt. Bitte versuchen Sie es später noch einmal“:

Es ist nicht gut, ein WSOD (White Screen of Death) auf Ihrer Seite zu haben, daher verbessern wir dies und zeigen stattdessen eine gestaltete HTML-Seite an.
Ich habe eine gestaltete 500.html
-Seite im Root-Verzeichnis meiner Website, aus Performance-Gründen. Wir können eine gestaltete Drupal-Seite für den 500-Fehler verwenden, aber ich werde dieselbe Seite auch für 503/504-Fehler in Apache/Nginx wiederverwenden und es ist einfacher, diese Seite als einzelne HTML-Datei an einem Ort zu haben.

Jetzt müssen wir Code in unser benutzerdefiniertes Modul DrupalBook Custom (drupalbook_custom
) einfügen. In drupalbook_custom.services.yml
müssen wir einen Event Subscriber hinzufügen:
services:
drupalbook_custom.exception_subscriber:
class: Drupal\drupalbook_custom\EventSubscriber\SeoExceptionSubscriber
arguments: ['@config.factory']
tags:
- { name: event_subscriber, priority: -250 }
Hier ist der Code für 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;
/**
* Ersetzt Drupal\Core\EventSubscriber\FinalExceptionSubscriber 500 Fehler.
*/
class SeoExceptionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Konfigurationseinstellungen.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected ConfigFactoryInterface $configFactory;
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* Behandelt alle nicht abgefangenen Exceptions und gibt eine eigene HTML-Antwort zurück.
*/
public function onException(ExceptionEvent $event): void {
// Zeige den normalen Drupal Stack Trace, wenn die Seite im VERBOSE-Modus ist.
if ($this->isErrorLevelVerbose()) {
return;
}
$exception = $event->getThrowable();
// Basisnachricht (bei Bedarf für den Verbose-Modus erweitern).
$error = Error::decodeException($exception);
$message = new FormattableMarkup('@message', [
'@message' => $error['!message'] ?? $this->t('Die Website hat einen unerwarteten Fehler festgestellt.'),
]);
$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']);
// Zusätzliche Header wie Retry-After beibehalten, falls vorhanden.
if ($exception instanceof HttpExceptionInterface) {
$response->headers->add($exception->getHeaders());
}
// Antwort senden und weitere Subscriber (inkl. Core) stoppen.
$event->setResponse($response);
$event->stopPropagation();
}
/**
* Liest web/500.html und ersetzt das {{ message }}-Token falls vorhanden.
*/
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);
}
// Sichere Fallback-Lösung, falls Template fehlt.
return '<html><head><title>500</title></head><body>'
. Markup::create($message)
. '</body></html>';
}
/**
* TRUE, wenn das Fehlerlevel auf "Verbose" steht.
*
* Spiegelung von \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 {
// Priorität -250 → läuft direkt vor Drupals FinalExceptionSubscriber (-256).
$events[KernelEvents::EXCEPTION][] = ['onException', -250];
return $events;
}
}
Diese Subscriber-Klasse, SeoExceptionSubscriber
, fängt alle nicht abgefangenen Exceptions innerhalb von Drupal ab. Sie prüft, ob Ihre Drupal-Seite auf „Verbose“-Fehlerberichterstattung eingestellt ist. Falls ja, lässt sie Drupal die Standard-Fehlermeldungen anzeigen. Ist die Seite jedoch nicht im „Verbose“-Modus (typisch für Produktionsumgebungen), wird die Exception abgefangen und eine benutzerfreundliche Fehlermeldung vorbereitet.
Sie liest speziell Ihre individuelle 500.html
-Datei, die sich im Root-Verzeichnis Ihrer Drupal-Installation befindet. Die Fehlermeldung wird dynamisch über das Platzhalter-Token {{ message }}
in den HTML-Inhalt eingefügt, wodurch die angezeigte Seite sowohl informativ als auch optisch konsistent bleibt.
Darüber hinaus sorgt der Subscriber dafür, dass Drupals Standard-Error-Handler nicht weiter ausgeführt wird. So wird die Drupal-Fehlerseite nicht über Ihre eigene HTML-Seite geschrieben. Mit einer Priorität von -250
wird der Subscriber direkt vor Drupals Core-Fehlerbehandler ausgeführt und überschreibt somit das Standardverhalten von Drupal.
Für Ihre lokale Entwicklungsumgebung können Sie Einstellungen setzen, damit Fehler statt der 500-Fehlerseite angezeigt werden.
settings.php
:
$config['system.logging']['error_level'] = 'verbose';
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
Manchmal ist Drupal nicht erreichbar, dann müssen Sie zusätzliche Konfigurationen für Ihren Webserver oder Ihre Cloud vornehmen.
500-Fehlerseite in Apache hinzufügen
Um Ihre vorhandene 500.html
-Fehlerseite aus dem Dokumentenstamm Ihrer Seite bei HTTP-Fehlern 500–504 auszuliefern, müssen Sie Apache entsprechend konfigurieren. Nachfolgend zwei einfache Möglichkeiten:
1. Verwendung der Apache Virtual Host-Konfiguration (empfohlen)
Bearbeiten Sie die Virtual Host-Konfigurationsdatei Ihrer Seite (normalerweise unter /etc/apache2/sites-available/your-site.conf
) und fügen Sie folgende Direktiven innerhalb des <VirtualHost>
-Blocks hinzu:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Anschließend Apache neu laden, um die Änderungen zu übernehmen:
sudo systemctl reload apache2
2. Verwendung der .htaccess
-Datei
Wenn Sie lieber die .htaccess
-Datei (im Dokumentenstamm Ihrer Website) nutzen, fügen Sie diese Zeilen ein:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Stellen Sie sicher, dass die Datei 500.html
im Root-Verzeichnis Ihrer Seite liegt und für Apache zugänglich und lesbar ist. Nach Anwendung dieser Einstellungen zeigt Apache für Fehler 500 bis 504 stets Ihre gestaltete HTML-Fehlerseite an.
500-Fehlerseite in Nginx hinzufügen
Um Nginx so zu konfigurieren, dass Ihre eigene 500.html
-Fehlerseite im Root-Verzeichnis Ihrer Seite für HTTP-Fehler (500–504) angezeigt wird, passen Sie die Server-Konfiguration wie folgt an:
Bearbeiten Sie die Nginx-Konfigurationsdatei Ihrer Seite (meist unter /etc/nginx/sites-available/your-site.conf
) und fügen Sie diese Direktiven im server {}
-Block ein:
error_page 500 501 502 503 504 /500.html;
location = /500.html {
root /var/www/html;
internal;
}
Stellen Sie sicher, dass der Pfad (/var/www/html
) auf das Dokumentenstammverzeichnis Ihrer Seite mit der 500.html
-Datei zeigt. Nach der Änderung Nginx-Konfiguration neu laden:
sudo nginx -s reload
Nun zeigt Nginx für HTTP-Statuscodes 500 bis 504 stets Ihre eigene HTML-Fehlerseite an.