Cómo crear páginas de error personalizadas en Nginx

Cada vez que Nginx encuentra un error al intentar procesar la solicitud de un cliente, devuelve un error. Cada error incluye un código de respuesta HTTP y una breve descripción. El error suele mostrarse al usuario mediante una sencilla página HTML predeterminada.

Afortunadamente, se puede configurar Nginx para que muestre páginas de error personalizadas a los usuarios de su sitio o aplicación web. Esto se puede lograr utilizando la directiva error_page de Nginx, que se utiliza para definir la URI que se mostrará para un error específico. También puede, opcionalmente, utilizarla para modificar el código de estado HTTP en los encabezados de respuesta enviados a un cliente.

Una página para todos los errores

Es posible configurar Nginx de modo que utilice una única página de error personalizada para todos los errores que devuelve a un cliente. Empiece por crear su página de error. A continuación se muestra un ejemplo, una página HTML sencilla que muestra el mensaje:

Estructura de directorios de alto nivel de archivos y carpetas:

/etc/nginx/
  |- nginx.conf
  `- errores/
     |- pagerror-redirector.conf
     `- pagerror-map.conf

/var/www/errores/
  `- errores.html

Cree una carpeta y algunos archivos dentro de ella para más adelante:

mkdir -p /etc/nginx/errores/ /var/www/errores/
touch /etc/nginx/errores/pagerror-{map,redirector}.conf /var/www/errores/errores.html

Añada un include en nginx.conf:

include /etc/nginx/errores/pagerror-map.conf;

dentro del bloque http {}. Ese bloque include debe estar antes los archivos include o la configuración del bloque server {}.

Añada lo siguiente en sus bloques de servidor:

include /etc/nginx/errores/pagerror-redirector.conf;

dentro del segmento del bloque server {} de cada host virtual, preferiblemente antes de los segmentos location.

Contenido de los archivos pagerror*.conf

Contenido del archivo pagerror-map.conf.

Esto es para el segmento del encabezado dentro de la página de error.

        map $status $status_text {
                400 'Solicitud incorrecta';
                401 'No autorizado';
                402 'Se requiere pago';
                403 'Prohibido/Restringido';
                404 'No encontrado';
                405 'Método no permitido';
                406 'No aceptable';
                407 'Se requiere autenticación de proxy';
                408 'Tiempo de espera de la solicitud agotado';
                409 'Conflicto';
                410 'Desaparecido';
                411 'Longitud requerida';
                412 'Condición previa fallida';
                413 'Carga útil demasiado grande';
                414 'URI demasiado largo';
                415 'Tipo de medio no compatible';
                416 'Rango no satisfactorio';
                417 'Expectativa fallida';
                418 'Soy una tetera';
                421 'Solicitud mal dirigida';
                422 'Entidad no procesable';
                423 'Bloqueado';
                424 'Dependencia fallida';
                425 'Demasiado pronto';
                426 'Actualización requerida';
                428 'Condición previa requerida';
                429 'Demasiadas solicitudes';
                431 'Campos de encabezado de solicitud demasiado grandes';
                451 'No disponible por motivos legales';
                500 'Error interno del servidor';
                501 'No implementado';
                502 'Puerta de enlace no válida';
                503 'Servicio no disponible';
                504 'Tiempo de espera de la puerta de enlace agotado';
                505 'Versión HTTP no compatible';
                506 'La variable también negocia';
                507 'Almacenamiento insuficiente';
                508 'Se ha detectado un bucle';
                510 'No ampliado';
                511 'Se requiere autenticación de red';
                default 'Algo ha ocurrido y el servidor está muy confundido...';
        }

Contenido del archivo pagerror-redirector.conf.

Captura la mayoría de los errores 400 y 500. También captura el tráfico de las máquinas backend con el indicador proxy_intercept_errors activado.

proxy_intercept_errors on;
error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;
location = /error.html {
    ssi on;
    internal;
    auth_basic off;
    root /var/www/error;
}

Contenido del archivo error.html.

Este archivo contiene algunos CSS básicos para que se vea bien. También cambia automáticamente los perfiles de color para los perfiles de cliente claros y oscuros.

<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>:root{color-scheme:light dark}body{font-family:sans-serif;background-color:#fff;color:#000;margin:1em}h1{font-weight:bolder;font-size:2.75em;display:contents}p{margin:10px 25px;line-height:1.5em}.footer{margin-top:2em;font-size:.95em}.footer-label{display:inline-block;font-weight:700;cursor:pointer;user-select:none;padding:.5em .5em .5em 1.75em;border-radius:.25em;position:relative;transition:background-color .2s}.footer-label::before{content:"";position:absolute;left:.6em;top:50%;width:.4em;height:.4em;border:solid currentColor;border-width:.15em .15em 0 0;transform-origin:center;transform:translateY(-50%) rotate(45deg);transition:transform .2s}.footer-toggle:checked+.footer-label::before{transform:translateY(-50%) rotate(135deg)}.footer .info-list{opacity:0;max-height:0;overflow:hidden;display:grid;grid-template-columns:max-content 1fr;gap:.5rem 1rem;list-style:none;padding:0;margin-top:0;transition:opacity .4s,max-height .4s,margin-top .4s}.footer-toggle:checked+.footer-label+.info-list{opacity:1;max-height:1000px;margin-top:1em}.footer .ilabel{font-weight:700;text-align:right;white-space:nowrap}.footer .ivalue{word-break:break-word}@media (max-width:600px){.footer .info-list{grid-template-columns:1fr}.footer .ilabel{text-align:left;margin-top:.75em}.footer .ivalue{margin-left:0}}@media (prefers-color-scheme:dark){body{background-color:#121212;color:#e0e0e0}a{color:#90caf9}hr{border-color:#444}.footer-label{background-color:#2a2a2a;color:#e0e0e0}.footer-label:hover{background-color:#3a3a3a}}@media (prefers-color-scheme:light){body{background-color:#fff;color:#000}a{color:#1a0dab}hr{border-color:#ccc}.footer-label{background-color:#eee;color:#000}.footer-label:hover{background-color:#ddd}}</style></head>
<body>
<h1><!--# echo var="status" default="" -->: <!--# echo var="status_text" default="Algo ha ocurrido..." --></h1>
<!--# if expr="$status = 400" --><p>Su solicitud no es válida. Revise la sintaxis o los parámetros de entrada.</p>
<!--# elif expr="$status = 401" --><p>Se requiere autenticación para acceder a este recurso. Inicie sesión o proporcione credenciales válidas.</p>
<!--# elif expr="$status = 402" --><p>El contenido solicitado requiere un pago antes de poder acceder a él.</p>
<!--# elif expr="$status = 403" --><p>No tiene permiso para acceder a este recurso. El acceso ha sido denegado explícitamente.</p>
<!--# elif expr="$status = 404" --><p>No se ha encontrado el contenido solicitado en este servidor. Compruebe la URL. <br />Es posible que la página se haya movido recientemente o que ni siquiera estuviera disponible en este servidor desde el principio.</p>
<!--# elif expr="$status = 405" --><p>El método utilizado en la solicitud no está permitido para este recurso (se utiliza POST cuando solo se acepta GET).</p>
<!--# elif expr="$status = 406" --><p>El tipo de contenido solicitado no es aceptable según los encabezados Accept enviados.</p>
<!--# elif expr="$status = 407" --><p>Se requiere autenticación de proxy antes de que se pueda completar esta solicitud.</p>
<!--# elif expr="$status = 408" --><p>El servidor agotó el tiempo de espera de la solicitud. Inténtelo de nuevo más tarde.</p>
<!--# elif expr="$status = 409" --><p>Se produjo un conflicto con el estado actual del recurso. Es posible que el contenido haya sido modificado por otra persona.</p>
<!--# elif expr="$status = 410" --><p>El recurso solicitado se ha eliminado de forma permanente y ya no está disponible.</p>
<!--# elif expr="$status = 411" --><p>El servidor requiere un encabezado «Content-Length» para procesar esta solicitud.</p>
<!--# elif expr="$status = 412" --><p>El servidor no ha cumplido una condición previa establecida en los encabezados de la solicitud.</p>
<!--# elif expr="$status = 413" --><p>La carga útil de la solicitud es demasiado grande para que el servidor la pueda manejar.</p>
<!--# elif expr="$status = 414" --><p>La URI solicitada es demasiado larga para que el servidor la pueda procesar.</p>
<!--# elif expr="$status = 415" --><p>El servidor no admite el tipo de medio de la solicitud.</p>
<!--# elif expr="$status = 416" --><p>El rango de bytes solicitado no se puede satisfacer (no es válido o está fuera de los límites del recurso).</p>
<!--# elif expr="$status = 417" --><p>El servidor no puede cumplir las expectativas especificadas en el encabezado «Expect».</p>
<!--# elif expr="$status = 418" --><p>Hmmmm... el servidor se niega a preparar café porque, en realidad, es una tetera.</p>
<!--# elif expr="$status = 421" --><p>Su solicitud se ha dirigido a un servidor que no puede generar una respuesta adecuada.</p>
<!--# elif expr="$status = 422" --><p>El servidor entiende el contenido, pero no puede procesar las instrucciones (error de validación de datos o error semántico).</p>
<!--# elif expr="$status = 423" --><p>El recurso está bloqueado actualmente y no se puede acceder a él ni modificarlo.</p>
<!--# elif expr="$status = 424" --><p>Esta solicitud falló porque falló una solicitud anterior de la que dependía.</p>
<!--# elif expr="$status = 425" --><p>El servidor no está dispuesto a arriesgarse a procesar esa solicitud, ya que podría repetirse.</p>
<!--# elif expr="$status = 426" --><p>Debe actualizar a una versión diferente del protocolo para acceder a este recurso.</p>
<!--# elif expr="$status = 428" --><p>Esta solicitud requiere encabezados condicionales para evitar sobrescrituras accidentales.</p>
<!--# elif expr="$status = 429" --><h2>Su cliente está siendo limitado o restringido.</h2><p>Se han enviado demasiadas solicitudes desde su cliente al servidor en un periodo de tiempo muy corto. <br/><br /><b>Reduzca la velocidad de sus conexiones a este servidor.</p>
<!--# elif expr="$status = 431" --><p>Uno o más encabezados de solicitud son demasiado grandes para que el servidor los procese.</p>
<!--# elif expr="$status = 451" --><p>Este contenido no está disponible o está restringido por motivos legales. Esto podría deberse a censura o a una orden judicial o legal.</p>
<!--# elif expr="$status = 500" --><p>El servidor ha encontrado un error interno. No es culpa suya.</p>
<!--# elif expr="$status = 501" --><p>El servidor backend no admite la funcionalidad necesaria para satisfacer la solicitud.</p>
<!--# elif expr="$status = 502" --><p>El balanceador de carga recibió una respuesta no válida de los servidores backend ascendentes. Se trata de un problema interno, no un problema suyo. Vuelva a intentarlo más tarde.
<!--# elif expr="$status = 503" --><p>El servidor del balanceador de carga no puede gestionar temporalmente la solicitud que ha enviado. Esto suele deberse a una sobrecarga, problemas en el backend o tareas de mantenimiento. Vuelva a intentarlo más tarde.</p>
<!--# elif expr="$status = 504" --><p>El balanceador de carga ha experimentado un evento de tiempo de espera agotado mientras esperaba una respuesta del servidor ascendente. Se trata de un problema interno, no un problema suyo. Vuelva a intentarlo más tarde.</p>
<!--# elif expr="$status = 505" --><p>La versión del protocolo HTTP utilizada en la solicitud no es compatible ni está permitida.</p>
<!--# elif expr="$status = 506" --><p>El servidor tiene un error de configuración interno (fallo de negociación).</p>
<!--# elif expr="$status = 507" --><p>El servidor no tiene suficiente espacio de almacenamiento o recursos para satisfacer su solicitud.</p>
<!--# elif expr="$status = 508" --><p>Se ha detectado un bucle infinito durante el procesamiento de la solicitud; se ha terminado la conexión.</p>
<!--# elif expr="$status = 510" --><p>Se requieren más extensiones para satisfacer la solicitud.</p>
<!--# elif expr="$status = 511" --><p>Se requiere autenticación de red antes de conceder el acceso.</p>
<!--# else --><p>Se ha devuelto un código de estado inesperado. No sabemos con certeza qué ha ocurrido...</p><!--# endif -->
<hr noshade /><div class="footer"><input type="checkbox" id="toggle-footer" class="footer-toggle" hidden><label for="toggle-footer" class="footer-label">Información de la solicitud del cliente</label><ul class="info-list"><li class="ilabel">Dirección IP:</li><li class="ivalue"><!--# echo var="remote_addr" --></li><li class="ilabel">Agente de usuario:</li><li class="ivalue"><!--# echo var="http_user_agent" --></li><li class="ilabel">Método de la solicitud:</li><li class="ivalue"><!--# echo var="request_method" --></li><li class="ilabel">Servidor:</li><li class="ivalue"><!--# echo var="server_name" --></li><li class="ilabel">URL de la solicitud:</li><li class="ivalue"><!--# echo var="request_uri" --></li><li class="ilabel">Cadena de consulta:</li><li class="ivalue"><!--# echo var="query_string" --></li></ul></div></body></html>

Guarde el contenido de esos archivos y reinicie nginx.

Cuando esté correctamente configurado, sus clientes verán el siguiente tipo de página de error. Así es como se ve en un tema oscuro con un error 404:

Con información de potatoforinter.net