Páginas de error personalizadas de Nginx: mejora la experiencia del usuario

Configura páginas de error personalizadas útiles de Nginx para respuestas 404, 403 y 50x sin ocultar fallos reales.

Páginas de error personalizadas de Nginx: mejora la experiencia del usuario

Las páginas de error personalizadas de Nginx convierten un fallo en bruto en un paso claro a seguir. No arreglan el enlace roto, el archivo faltante o la aplicación upstream que falló. Hacen algo más pequeño pero igualmente valioso: le dicen al visitante qué sucedió en un lenguaje sencillo y evitan que se sientan expulsados de tu sitio.

Esto importa durante errores comunes. Alguien sigue un enlace antiguo de documentación y obtiene un 404. Una ruta de archivo privado devuelve 403. Tu aplicación se reinicia durante el despliegue y Nginx ve brevemente un 502. Sin una página personalizada, los usuarios pueden ver una respuesta predeterminada del servidor que parece abrupta o técnica. Con una buena página estática, saben si buscar, retroceder, reintentar o esperar.

Las mejores páginas de error son herramientas operativas aburridas. Son estáticas, rápidas, accesibles y honestas. No ocultan las interrupciones a la monitorización, y no exponen detalles internos a los usuarios.

Comienza con los errores que la gente realmente ve

No necesitas diseñar una página para cada código de estado HTTP. Comienza con los comunes.

404 No encontrado es la primera página a personalizar. Aparece cuando la URL solicitada no coincide con un archivo, ruta o ubicación de Nginx que devuelva contenido. Enlaces antiguos, publicaciones renombradas, páginas de documentación eliminadas y URLs escritas a mano llevan aquí.

Una página 404 útil dice algo como: "No pudimos encontrar esa página." Luego ofrece un camino de regreso a la página de inicio, índice de documentación, área de producto o página de búsqueda. No culpes al usuario. La URL puede haber estado mal mucho antes de que hicieran clic.

403 Prohibido es diferente. Nginx entendió la solicitud pero no la servirá. Las causas incluyen permisos de archivos, reglas de acceso, listado de directorios deshabilitado, reglas de permitir/denegar IP o requisitos de autenticación. Una página 403 debe ser tranquila y breve. Si el recurso es privado, dilo. Si los usuarios pueden necesitar acceso, indícales la ruta de inicio de sesión o soporte correcta.

Para sitios con aplicaciones, maneja los errores 50x con cuidado:

  • 500 Error interno del servidor generalmente significa que la aplicación falló al procesar la solicitud.
  • 502 Puerta de enlace incorrecta a menudo significa que Nginx no recibió una respuesta válida del servicio upstream.
  • 503 Servicio no disponible es útil para mantenimiento, sobrecarga o un servicio deliberadamente no disponible.
  • 504 Tiempo de espera de puerta de enlace significa que Nginx esperó demasiado tiempo por la respuesta upstream.

Un ejemplo de panel de SaaS: tu proceso de aplicación se reinicia durante el despliegue. Por unos segundos, Nginx no puede conectarse al upstream y los usuarios ven un 502. Una página personalizada puede decir: "El panel no está disponible temporalmente. Por favor, actualiza en un minuto." Eso no es perfecto, pero es más claro que un error de puerta de enlace predeterminado.

Crea archivos de error estáticos

Mantén las páginas de error fuera de la cadena de dependencias de la aplicación. Si la base de datos está caída, la página 500 aún debe cargarse. Si la aplicación Node, Python, Ruby o PHP no está saludable, Nginx aún debe servir la alternativa estática.

Una estructura de archivos simple podría ser:

/var/www/example.com/public/
  index.html
  assets/
/var/www/example.com/errors/
  404.html
  403.html
  50x.html

Mantén el HTML ligero. Evita scripts de terceros, imágenes pesadas, paquetes de aplicaciones del lado del cliente y cualquier cosa que llame al backend roto. Un pequeño archivo CSS está bien si Nginx puede servirlo directamente.

Un 404.html mínimo podría ser:

<!doctype html>
<html lang="es">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Página no encontrada</title>
</head>
<body>
  <main>
    <h1>Página no encontrada</h1>
    <p>La página puede haberse movido o el enlace puede estar desactualizado.</p>
    <p><a href="/">Ir a la página de inicio</a></p>
  </main>
</body>
</html>

Eso es suficiente. Puedes estilizarlo para que coincida con tu sitio, pero el mensaje y los enlaces importan más que la decoración.

Conecta las páginas con error_page

En Nginx, la directiva error_page asigna uno o más códigos de estado a una URI. Un bloque de servidor básico se ve así:

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;

    error_page 404 /errors/404.html;
    error_page 403 /errors/403.html;
    error_page 500 502 503 504 /errors/50x.html;

    location /errors/ {
        internal;
        root /var/www/example.com;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

La resolución de ruta es la parte que confunde a la gente. En este ejemplo, las solicitudes bajo /errors/ usan root /var/www/example.com;, por lo que /errors/404.html se asigna a /var/www/example.com/errors/404.html.

La directiva internal significa que los clientes externos no pueden solicitar /errors/404.html directamente como una URL normal. Nginx aún puede servirlo internamente al manejar un error.

Después de editar la configuración, prueba y recarga:

sudo nginx -t
sudo systemctl reload nginx

Luego prueba el comportamiento:

curl -I http://example.com/pagina-definitivamente-faltante
curl -s http://example.com/pagina-definitivamente-faltante | head

El estado aún debe ser 404, aunque el cuerpo sea tu HTML amigable. Un error común es devolver accidentalmente 200 OK para una página faltante, lo que hace que la monitorización y los motores de búsqueda piensen que la URL faltante es una página real.

Páginas personalizadas detrás de un proxy inverso

Si Nginx actúa como proxy hacia una aplicación upstream, la aplicación puede devolver sus propias respuestas de error. Por defecto, las respuestas proxy se pasan generalmente al cliente. Para que Nginx intercepte las respuestas de error upstream y use tus reglas error_page, habilita proxy_intercept_errors en el contexto relevante.

location /app/ {
    proxy_pass http://app_backend;
    proxy_intercept_errors on;

    error_page 502 503 504 /errors/50x.html;
}

La documentación de Nginx describe proxy_intercept_errors como aplicable a respuestas proxy con códigos de estado mayores o iguales a 300, que luego pueden redirigirse a Nginx para el procesamiento de error_page. En la práctica, no lo actives en todas partes sin pensar.

Para páginas de navegador, interceptar 502 o 503 suele ser útil. Para APIs JSON, puede ser incorrecto. Los clientes de API generalmente esperan un cuerpo de error JSON estructurado, no una página HTML. Puedes necesitar ubicaciones separadas:

location /api/ {
    proxy_pass http://api_backend;
    proxy_intercept_errors off;
}

location /dashboard/ {
    proxy_pass http://app_backend;
    proxy_intercept_errors on;
    error_page 502 503 504 /errors/50x.html;
}

Esa división mantiene las páginas orientadas a humanos amigables mientras preserva los errores de API legibles por máquina.

Preserva el código de estado correcto

Nginx te permite cambiar el código de respuesta con error_page, pero hazlo solo cuando sea intencionado. Esta es una sintaxis válida:

error_page 404 =200 /fallback.html;

Para la mayoría de los sitios web, eso sería una mala idea. Una página faltante debe seguir siendo un 404. Los motores de búsqueda, las comprobaciones de disponibilidad, la analítica y los usuarios se benefician de la verdad.

Hay casos legítimos para cambiar códigos, como enrutar ciertos errores a una ubicación nombrada o devolver una página de mantenimiento con 503. Pero como regla predeterminada, preserva el estado de error original.

Para mantenimiento, puedes ser explícito:

location / {
    return 503;
}

error_page 503 /errors/maintenance.html;

location = /errors/maintenance.html {
    root /var/www/example.com;
    internal;
}

Si usas un CDN o balanceador de carga frente a Nginx, recuerda que puede tener su propio comportamiento de página de error. Decide qué capa posee qué errores. De lo contrario, puedes probar Nginx directamente y ver una página, mientras que los usuarios detrás del CDN ven otra.

Escribe páginas de error para humanos

El contenido debe responder tres preguntas rápidamente:

  • ¿Qué pasó?
  • ¿Es temporal?
  • ¿Qué puedo hacer a continuación?

Para un 404, los pasos útiles son buscar, página de inicio, índice de documentación o contactar al soporte si la página faltante debería existir. Para un 503, la guía útil es reintentar más tarde o consultar una página de estado. Para un 403, indica instrucciones de inicio de sesión o solicitud de acceso si corresponde.

Evita trazas de pila, nombres de host upstream, rutas del sistema de archivos, versiones de paquetes, IPs internas, IDs de solicitud sin explicación y detalles de incidentes. Un ID de solicitud puede ser útil si el soporte puede usarlo, pero etiquétalo claramente:

<p>Si contactas al soporte, incluye este ID de solicitud: <code>$request_id</code></p>

Para inyectar variables como $request_id, necesitas un patrón de configuración que lo soporte. Los archivos HTML estáticos no expandirán las variables de Nginx por sí mismos. Muchos equipos mantienen páginas de error públicas estáticas y confían en los registros para los IDs de solicitud.

La accesibilidad es parte de la utilidad. Usa un h1 claro, contraste legible, enlaces normales y texto plano. No hagas que la única acción de recuperación sea un pequeño icono o un botón basado en scripts.

Prueba las páginas a propósito

No esperes a que los usuarios encuentren tus páginas de error. Pruébalas después de cada cambio.

Para 404:

curl -i https://example.com/pagina-inexistente

Para 403, crea una ubicación de prueba controlada o usa un archivo de prueba privado. No aflojes los permisos de producción solo para provocar un error.

Para 502 o 503, prueba en staging apuntando una ubicación a un upstream no disponible:

location /prueba-upstream-roto/ {
    proxy_pass http://127.0.0.1:59999;
    proxy_intercept_errors on;
    error_page 502 503 504 /errors/50x.html;
}

Luego solicítalo y confirma tanto el código de estado como el cuerpo:

curl -i https://staging.example.com/prueba-upstream-roto/

También observa los registros:

sudo tail -f /var/log/nginx/error.log /var/log/nginx/access.log

Una página de error de buen aspecto no debe borrar la señal operativa. Tus alertas aún deben dispararse cuando aumenten las tasas de 50x.

Las páginas de error personalizadas de Nginx son una tarea de configuración pequeña con un impacto real en el usuario. Comienza con 404, 403 y los errores 50x comunes. Sirve archivos estáticos directamente desde Nginx. Preserva códigos de estado precisos. Usa proxy_intercept_errors solo donde los fallbacks HTML tengan sentido. Luego prueba las páginas de la misma manera que pruebas cualquier otro comportamiento de producción.