Skip to main content

Pro Tips — Failover, Error Pages, and Debugging

Real-world expert tips for operating Nginx + Tomcat. This chapter covers fallback handling when upstream fails, custom error pages, health checks, and debugging integration issues.


Fallback Handling for Upstream Failures​

Provide meaningful responses to users when Tomcat is down or returning errors.

proxy_next_upstream — Automatic Retry​

upstream tomcat_backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup; # Backup instance if main fails

keepalive 32;
}

server {
location / {
proxy_pass http://tomcat_backend;

# Retry next server on upstream failure
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
}
}

Error Interception — Custom Error Pages​

server {
listen 443 ssl;
server_name example.com;

proxy_intercept_errors on;

location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}

error_page 500 /error/500.html;
error_page 502 /error/502.html;
error_page 503 /error/503.html;
error_page 504 /error/504.html;

location /error/ {
root /var/www/myapp;
internal;
add_header Cache-Control "no-store";
}

# JSON error response for API clients
location /api/ {
proxy_pass http://127.0.0.1:8080;
error_page 500 502 503 504 @api_error;
}

location @api_error {
default_type application/json;
return 503 '{"error":"service_unavailable","message":"Service temporarily unavailable."}';
}
}

Static Fallback Page​

<!-- /var/www/myapp/error/502.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="10">
<title>Service Unavailable</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; }
h1 { color: #e74c3c; }
</style>
</head>
<body>
<h1>Service Temporarily Unavailable</h1>
<p>The page will automatically refresh in 10 seconds.</p>
<p>Contact: support@example.com</p>
</body>
</html>

Health Check Configuration​

Tomcat Health Check Endpoint (Spring Boot)​

# application.yml
management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: never
# External health check script
#!/bin/bash
HEALTH=$(curl -sf http://localhost:8080/actuator/health | grep -c '"status":"UP"')
if [ "$HEALTH" -eq 0 ]; then
echo "Tomcat unhealthy — restarting"
systemctl restart tomcat
fi

Internal Health Check Endpoint for Nginx​

server {
listen 8888;
server_name localhost;
allow 127.0.0.1;
deny all;

location /health {
proxy_pass http://127.0.0.1:8080/actuator/health;
proxy_connect_timeout 3s;
proxy_read_timeout 5s;
access_log off;
}
}

Debugging — Issue-by-Issue Diagnosis​

502 Bad Gateway​

# 1. Check Tomcat port listening
ss -tlnp | grep 8080
# No result = Tomcat not running

# 2. Direct Tomcat access test
curl -v http://localhost:8080/
# No response = Tomcat problem

# 3. Check Nginx error log
tail -50 /var/log/nginx/error.log
# "connect() failed (111: Connection refused)" → Tomcat down
# "upstream timed out" → Tomcat slow response

504 Gateway Timeout​

# Check Tomcat processing time (last field in access log: response time ms)
tail -100 /opt/tomcat/latest/logs/localhost_access_log*.txt | \
awk '{print $NF}' | sort -n | tail -10

# Check and adjust Nginx read_timeout
grep "proxy_read_timeout" /etc/nginx/sites-available/myapp.conf

# Check Tomcat thread status
curl -u admin:password http://localhost:8080/manager/status

Connection Reset / Drops​

# Check TIME_WAIT excess
ss -s | grep TIME-WAIT

# Check keepalive mismatch
# If Tomcat closes the connection before Nginx, Nginx returns 502

# Fix: Set Tomcat keepAliveTimeout higher than Nginx keepalive_timeout
# Nginx: keepalive_timeout 65s;
# Tomcat: keepAliveTimeout="70000" (70 seconds)

Debug Headers for Development​

server {
# Add debug headers to responses (development environment)
add_header X-Upstream-Addr $upstream_addr;
add_header X-Upstream-Status $upstream_status;
add_header X-Upstream-Time $upstream_response_time;
add_header X-Request-ID $request_id;

location / {
proxy_pass http://tomcat_backend;
proxy_set_header X-Request-ID $request_id;
}
}

Log Correlation — Tracing Nginx ↔ Tomcat​

log_format trace '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'req_id=$request_id '
'upstream_time=$upstream_response_time';

access_log /var/log/nginx/myapp_access.log trace;
// Spring Boot — include X-Request-ID in logs
@Component
public class RequestIdFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
String requestId = ((HttpServletRequest) req).getHeader("X-Request-ID");
MDC.put("requestId", requestId != null ? requestId : "none");
try {
chain.doFilter(req, res);
} finally {
MDC.clear();
}
}
}

Maintenance Mode​

# touch /var/www/maintenance.flag  → enable maintenance mode
# rm /var/www/maintenance.flag → disable maintenance mode

server {
listen 443 ssl;
server_name example.com;

location / {
if (-f /var/www/maintenance.flag) {
return 503;
}

proxy_pass http://tomcat_backend;
}

error_page 503 @maintenance;
location @maintenance {
root /var/www/myapp;
rewrite ^(.*)$ /error/maintenance.html break;
add_header Cache-Control "no-store";
add_header Retry-After 3600;
}
}

Integration Health Check Script​

#!/bin/bash
echo "=== Nginx + Tomcat Integration Checklist ==="

echo -n "Nginx config: "
nginx -t 2>&1 | grep -q "successful" && echo "✅ OK" || echo "❌ Error"

echo -n "Nginx running: "
systemctl is-active nginx > /dev/null && echo "✅ Running" || echo "❌ Stopped"

echo -n "Tomcat running: "
systemctl is-active tomcat > /dev/null && echo "✅ Running" || echo "❌ Stopped"

echo -n "Tomcat :8080: "
ss -tlnp | grep -q ":8080" && echo "✅ Listening" || echo "❌ Not listening"

HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" https://localhost/)
echo "HTTP status: $HTTP_CODE (expected 200)"

RESPONSE_TIME=$(curl -sk -o /dev/null -w "%{time_total}" https://localhost/)
echo "Response time: ${RESPONSE_TIME}s"

echo "=== Checklist complete ==="

Summary​

SituationSolution
Tomcat downproxy_next_upstream error timeout + backup server
5xx error pagesproxy_intercept_errors on + error_page
502 occurringCheck Tomcat listening → direct access test → Nginx logs
504 occurringIncrease proxy_read_timeout → analyze Tomcat processing time
keepalive dropsTomcat keepAliveTimeout > Nginx keepalive_timeout
Maintenance modeFlag file + if (-f) + return 503