How to Configure Apache Reverse Proxy to Show Custom Error Page When Backend Server (Tomcat) is Down


2 views

When using Apache as a reverse proxy for applications like Tomcat, one common pain point developers face is handling graceful degradation when the backend service becomes unavailable. The default behavior of returning 502 Bad Gateway or 503 Service Unavailable errors isn't always ideal for production environments.

Here's the basic reverse proxy setup that most implementations start with:

ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/

Apache provides several ways to handle backend failures gracefully. The most robust solution combines ErrorDocument with ProxyErrorOverride:

<VirtualHost *:80>
    ServerName yourdomain.com
    
    ProxyRequests Off
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
    
    # Enable error override
    ProxyErrorOverride On
    
    # Custom error documents
    ErrorDocument 502 /maintenance.html
    ErrorDocument 503 /maintenance.html
    ErrorDocument 504 /maintenance.html
    
    # Make sure Apache can serve the error page
    Alias /maintenance.html /var/www/html/maintenance.html
</VirtualHost>

For more sophisticated setups, you might want to check backend availability before showing the error page. Here's an approach using mod_rewrite:

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/maintenance.html$
RewriteCond %{DOCUMENT_ROOT}/maintenance.html -f
RewriteCond expr "! %{HTTP_HOST} -strmatch '*\.example\.com'"
RewriteRule ^.*$ /maintenance.html [R=503,L]

Your maintenance.html should be:

  • A static HTML file (no external dependencies)
  • Lightweight (under 10KB)
  • Contain appropriate meta tags for SEO
  • Include basic branding and contact information

Example maintenance.html:

<!DOCTYPE html>
<html>
<head>
    <title>Service Maintenance</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="robots" content="noindex">
    <style>
        body { font-family: sans-serif; text-align: center; padding: 50px; }
        h1 { font-size: 2em; }
    </style>
</head>
<body>
    <h1>We're undergoing scheduled maintenance</h1>
    <p>The service will be back shortly. Thank you for your patience.</p>
</body>
</html>

To verify your setup works properly:

  1. Place your maintenance.html in the specified location
  2. Stop your Tomcat server
  3. Make requests to your Apache reverse proxy
  4. Check that you receive the maintenance page with 503 status code

When using Apache as a reverse proxy for Tomcat with standard configuration:

ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/

Apache will return a 503 Service Unavailable error when the backend Tomcat server is down. This default behavior isn't user-friendly and doesn't provide any contextual information.

Apache's ProxyErrorOverride directive combined with custom error documents provides the solution:

ProxyRequests Off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
ProxyErrorOverride On
ErrorDocument 503 /maintenance.html

Create a simple HTML file at your document root (e.g., /var/www/html/maintenance.html):

<!DOCTYPE html>
<html>
<head>
    <title>Service Maintenance</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
        h1 { color: #d9534f; }
        p { font-size: 1.2em; }
    </style>
</head>
<body>
    <h1>Under Maintenance</h1>
    <p>We're currently performing scheduled maintenance.</p>
    <p>Please check back soon.</p>
</body>
</html>

For more control, you can use RewriteCond to check backend availability:

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/maintenance\.html$ [NC]
RewriteCond %{ENV:REDIRECT_STATUS} =503
RewriteRule ^(.*)$ /maintenance.html [R=503,L]

You might want different pages for different error conditions:

ErrorDocument 502 /bad-gateway.html
ErrorDocument 503 /maintenance.html
ErrorDocument 504 /gateway-timeout.html

For high-traffic sites, consider caching the error page:

<FilesMatch "maintenance\.html$">
    Header set Cache-Control "max-age=3600, public"
</FilesMatch>