Adding 500 error page in Drupal using Event Subscriber
Often we face 500 error page when Drupal, services or another sites are not available. When we see 500 (or 501-504) error page. In Drupal we use Exceptions to check some critical code was executed. If we got an error, for exaple in HTTP request to another site when Drupal will show this error "The website encountered an unexpected error. Please try again later":

It's not good to have WSOD (white screen of death) on your site, so let's ehnance it and will show styled HTML page instead.
I have 500.html styled page in the root of my site, cause of performance reason. We can use styled Drupal page for 500 error, but I also will re use the same page for 503/504 error in Apache/Nginx and it will be easier to keep this page in one place single HTML page.

Now we need to add code in our custom module DrupalBook Custom (drupalbook_custom). In drupalbook_custom.services.yml we need to add Event Subscriber:
services:
drupalbook_custom.exception_subscriber:
class: Drupal\drupalbook_custom\EventSubscriber\SeoExceptionSubscriber
arguments: ['@config.factory']
tags:
- { name: event_subscriber, priority: -250 }
Here is code for 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;
/**
* Replaces Drupal\Core\EventSubscriber\FinalExceptionSubscriber 500 error.
*/
class SeoExceptionSubscriber implements EventSubscriberInterface {
use StringTranslationTrait;
/**
* Configs for settings.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected ConfigFactoryInterface $configFactory;
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* Handles any uncaught exception and returns a custom HTML response.
*/
public function onException(ExceptionEvent $event): void {
// Show the normal Drupal stack trace when the site is in VERBOSE mode.
if ($this->isErrorLevelVerbose()) {
return;
}
$exception = $event->getThrowable();
// Basic message (extend for verbose mode if needed).
$error = Error::decodeException($exception);
$message = new FormattableMarkup('@message', [
'@message' => $error['!message'] ?? $this->t('The website encountered an unexpected error.'),
]);
$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']);
// Preserve extra headers such as Retry-After when present.
if ($exception instanceof HttpExceptionInterface) {
$response->headers->add($exception->getHeaders());
}
// Send the response and stop further subscribers (incl. core’s).
$event->setResponse($response);
$event->stopPropagation();
}
/**
* Reads web/500.html and injects a {{ message }} token if present.
*/
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);
}
// Safe fallback if template missing.
return '<html><head><title>500</title></head><body>'
. Markup::create($message)
. '</body></html>';
}
/**
* TRUE when error level is set to “Verbose”.
*
* Mirrors \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 {
// Priority -250 → runs just before core’s FinalExceptionSubscriber (-256).
$events[KernelEvents::EXCEPTION][] = ['onException', -250];
return $events;
}
}
This subscriber class, SeoExceptionSubscriber
, intercepts all uncaught exceptions within Drupal. It checks whether your Drupal site is set to verbose error reporting mode, and if it is, allows Drupal to display its standard detailed error messages. However, if the site is not in verbose mode (typical in a production environment), it catches the exception and prepares a user-friendly error message instead.
Specifically, it reads your custom 500.html
file located at the root of your Drupal installation. It dynamically inserts the error message into the HTML content by replacing a placeholder token, {{ message }}
, ensuring the displayed page is both informative and visually consistent.
Additionally, the subscriber explicitly stops Drupal’s default error handler from processing further. This ensures Drupal’s built-in error page does not overwrite your customized HTML page. By defining the subscriber with a priority of -250
, it executes just before Drupal core's built-in error subscriber, effectively overriding Drupal’s default behavior.
For your local environment you can insert settings to display errors instead of 500 error page.
settings.php:
$config['system.logging']['error_level'] = 'verbose';
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
Sometimes Drupal is not reachible, then you will be needed to set up additional configs for your web server or Cloud.
Adding 500 error page in Apache
To serve your existing 500.html
error page from your site's document root whenever HTTP errors 500–504 occur, you need to configure Apache accordingly. Below are two simple ways to achieve this:
1. Using Apache Virtual Host Configuration (recommended)
Edit your site's virtual host configuration file (typically located at /etc/apache2/sites-available/your-site.conf
) and add the following directives within the <VirtualHost>
block:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Then, reload Apache to apply the changes:
sudo systemctl reload apache2
2. Using the .htaccess
file
If you prefer using the .htaccess
file (located in your site's document root), simply insert these lines:
ErrorDocument 500 /500.html
ErrorDocument 501 /500.html
ErrorDocument 502 /500.html
ErrorDocument 503 /500.html
ErrorDocument 504 /500.html
Make sure the file 500.html
is placed in the root directory of your site, accessible and readable by Apache. After applying these settings, Apache will display your styled HTML error page consistently for errors 500 through 504.
Adding 500 error page in Nginx
To configure Nginx to serve your custom 500.html
error page located in your site's root directory for HTTP errors (500–504), update your site's Nginx server configuration as follows:
Edit your site's Nginx configuration file (typically found at /etc/nginx/sites-available/your-site.conf
) and insert the following directives inside your server {}
block:
error_page 500 501 502 503 504 /500.html;
location = /500.html {
root /var/www/html;
internal;
}
Ensure the path (/var/www/html
) correctly points to your site's document root containing the 500.html
file. After editing, reload Nginx configuration to apply your changes:
sudo nginx -s reload
Now Nginx will display your custom HTML error page consistently for HTTP error statuses 500 through 504.