ALB gibt 502 Bad Gateway zurück – Ursachen und Diagnose wenn Targets als 'Healthy' angezeigt werden
Der Application Load Balancer meldet 502, die Target Group zeigt alle Instanzen als 'Healthy' – und genau diese Kombination ist das Problem. Wer zuerst auf die Health Checks schaut und dort grüne Häkchen sieht, sucht danach meistens an der falschen Stelle weiter. Ein 502 vom ALB bedeutet nicht, dass die Instanz nicht erreichbar ist, sondern dass der ALB eine Antwort erhalten hat, die er nicht verarbeiten konnte.
TL;DR – ALB 502 Bad Gateway Schnellübersicht
| Ursache | Symptom | Erste Prüfung |
|---|---|---|
| Ungültige HTTP-Antwort vom Backend | 502 bei allen oder bestimmten Requests | Access Logs: target_status_code |
| Keep-Alive Timeout Mismatch | Intermittierende 502, kein App-Fehler im Log | Keep-Alive-Konfiguration des App-Servers prüfen |
| Verbindung vom Backend vorzeitig getrennt | 502 nach variabler Antwortzeit | ALB Access Log: target_status_code leer oder - |
| Protokoll-Mismatch (HTTP vs. HTTPS) | 502 sofort, TLS-Fehler im Backend-Log | Target Group Protokoll vs. App-Server-Port |
| Response Header zu groß | 502 bei spezifischen Endpunkten | Header-Größe der Antwort messen |
| Chunked Encoding Fehler | 502 bei großen Responses | Transfer-Encoding Header prüfen |
Wie der ALB Verbindungen verwaltet – Grundlage für die 502-Diagnose
Bevor die Diagnose beginnt, muss das Verbindungsmodell des ALB klar sein. Der ALB terminiert jede eingehende Client-Verbindung und baut eine separate Verbindung zum Backend-Target auf. Das sind zwei vollständig unabhängige TCP-Verbindungen. Ein 502 entsteht ausschließlich auf der Backend-Seite – also zwischen ALB und Target.
Health Checks und Request-Handling laufen über unterschiedliche Verbindungen. Eine Instanz kann Health-Check-Requests korrekt beantworten und trotzdem bei echten Requests einen 502 produzieren – etwa weil der Health-Check-Pfad eine triviale 200-Antwort liefert, während der eigentliche Endpunkt fehlerhafte HTTP-Header zurückgibt.
(App-Server)"] HC["Health Check
Verbindung"] REQ["Request
Verbindung"] Client -->|"TCP Verbindung 1
(terminiert am ALB)"| ALB ALB -->|"TCP Verbindung 2
(separate Verbindung)"| REQ REQ --> EC2 ALB -.->|"Unabhängig"| HC HC -.-> EC2 TG --> EC2 style HC stroke-dasharray: 5 5 style REQ fill:#f9f,stroke:#333
- Client → ALB: TCP-Verbindung wird vom ALB terminiert. Der Client kommuniziert ausschließlich mit dem ALB.
- ALB → Target: Separate TCP-Verbindung. Der ALB leitet den Request weiter und erwartet eine valide HTTP-Antwort.
- 502-Entstehung: Wenn das Target keine gültige HTTP-Antwort liefert, die Verbindung abbricht oder der ALB die Antwort nicht parsen kann, gibt der ALB dem Client einen 502 zurück.
- Health Check: Läuft parallel, auf einem konfigurierten Pfad. Ein erfolgreicher Health Check schließt einen 502 bei echten Requests nicht aus.
Schritt 1: ALB Access Logs aktivieren und auswerten
Ohne Access Logs ist die Diagnose eines ALB 502 reines Raten. Der entscheidende Feldwert ist target_status_code. Wenn dieses Feld einen Bindestrich (-) enthält, hat das Backend gar keine Antwort geliefert – die Verbindung wurde abgebrochen, bevor eine HTTP-Antwort übertragen wurde. Das ist ein anderes Problem als ein Backend, das aktiv einen Fehlercode zurückgibt.
Access Logs für einen bestehenden ALB aktivieren:
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/mein-alb/1234567890abcdef \
--attributes Key=access_logs.s3.enabled,Value=true \
Key=access_logs.s3.bucket,Value=mein-alb-logs-bucket \
Key=access_logs.s3.prefix,Value=alb-logs
Das S3-Bucket benötigt eine Bucket Policy, die dem ALB-Service-Account das Schreiben erlaubt. Die erforderliche Policy ist in der offiziellen AWS-Dokumentation für Elastic Load Balancing Access Logs beschrieben – der genaue Principal variiert je nach Region.
Nach dem Aktivieren der Logs relevante Einträge filtern. Mit AWS CLI und S3 Select oder direkt über Athena lassen sich die Logs effizient abfragen. Für einen schnellen ersten Blick:
aws s3 cp s3://mein-alb-logs-bucket/alb-logs/ ./alb-logs/ --recursive \
--exclude '*' --include '*.log.gz'
# Komprimierte Logs nach 502-Einträgen durchsuchen
zgrep '" 502 ' ./alb-logs/*.log.gz | head -50
Die kritischen Felder im Access Log für die 502-Diagnose sind elb_status_code (immer 502), target_status_code (was das Backend geantwortet hat oder -), und error_reason (seit neueren ALB-Versionen verfügbar, enthält den Grund für den 502).
Schritt 2: target_status_code auswerten – die entscheidende Weiche
Das Feld target_status_code im Access Log teilt die möglichen Ursachen in zwei klar getrennte Kategorien:
- target_status_code ist '-': Das Backend hat die Verbindung abgebrochen oder gar nicht geantwortet. Ursachen: Keep-Alive Timeout, Prozessabsturz, Verbindungslimit erreicht.
- target_status_code enthält einen Wert (z.B. 200, 500): Das Backend hat geantwortet, aber die Antwort war HTTP-technisch ungültig – fehlerhafte Header, ungültiges Chunked Encoding, oder zu große Header.
- error_reason Feld: Wenn vorhanden, liefert es den konkreten Fehlergrund direkt aus dem ALB, z.B.
TargetConnectionErrorCodeoder ähnliche Bezeichner.
Schritt 3: Keep-Alive Timeout Mismatch – die häufigste versteckte Ursache
Intermittierende 502-Fehler ohne App-Fehler im Backend-Log und mit leerem target_status_code deuten fast immer auf einen Keep-Alive Timeout Mismatch hin. Der ALB hält Verbindungen zu Backends offen und wiederverwendet sie. Wenn der App-Server seine Keep-Alive-Verbindung schließt, bevor der ALB das mitbekommt, sendet der ALB den nächsten Request auf einer bereits geschlossenen Verbindung – und erhält keine Antwort.
Das ist wie ein Taxifahrer, der aussteigt und die Tür zumacht, während der Fahrgast noch einsteigen will. Der Fahrgast sieht ein leeres Taxi – nicht einen Fahrer, der 'Nein' sagt.
Der ALB hat einen eigenen Idle Timeout (Standard: 60 Sekunden). Der App-Server muss seinen Keep-Alive Timeout auf einen Wert setzen, der höher ist als der ALB Idle Timeout. Sonst schließt der App-Server die Verbindung, bevor der ALB sie als inaktiv erkennt.
ALB Idle Timeout prüfen:
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/mein-alb/1234567890abcdef \
--query 'Attributes[?Key==`idle_timeout.timeout_seconds`]'
Idle Timeout anpassen (Beispiel: auf 120 Sekunden erhöhen):
aws elbv2 modify-load-balancer-attributes \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/mein-alb/1234567890abcdef \
--attributes Key=idle_timeout.timeout_seconds,Value=120
Für gängige App-Server gilt: Der Keep-Alive Timeout des App-Servers muss größer sein als der ALB Idle Timeout. Bei nginx ist das keepalive_timeout, bei Apache KeepAliveTimeout. Wer den ALB Idle Timeout auf 60 Sekunden lässt und nginx auf 65 Sekunden konfiguriert, ist auf der sicheren Seite.
Schritt 4: Protokoll-Mismatch zwischen Target Group und App-Server
Wenn die Target Group auf HTTPS konfiguriert ist, der App-Server aber nur HTTP auf dem Port hört – oder umgekehrt – bricht die Verbindung sofort ab. Der ALB versucht einen TLS-Handshake, der App-Server antwortet mit rohem HTTP, und der ALB kann die Antwort nicht interpretieren.
Target Group Protokoll prüfen:
aws elbv2 describe-target-groups \
--load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/mein-alb/1234567890abcdef \
--query 'TargetGroups[*].{Name:TargetGroupName,Protocol:Protocol,Port:Port}'
Wenn das Protokoll HTTPS ist, muss der App-Server TLS auf dem konfigurierten Port terminieren. Wenn HTTP, darf kein TLS auf dem Port aktiv sein. Das klingt offensichtlich, aber in der Praxis passiert dieser Fehler häufig nach Migrationen, wenn die Target Group neu erstellt wird und das Protokoll nicht explizit gesetzt wird.
Schritt 5: Ungültige HTTP-Antwort-Header vom Backend
Der ALB ist strikt bei der HTTP-Konformität. Antworten mit ungültigen Header-Werten, Header-Namen mit unerlaubten Zeichen, oder Responses, die gegen den HTTP/1.1-Standard verstoßen, werden mit einem 502 abgelehnt. Das Backend-Log zeigt in diesem Fall eine erfolgreiche Antwort – aus Sicht des App-Servers hat alles funktioniert.
Dieser Fall ist besonders tückisch: Das Backend-Log zeigt 200, der Client sieht 502, und der ALB Access Log zeigt target_status_code als 200. Das bedeutet: Der ALB hat die Antwort empfangen, aber beim Parsen der Headers ist etwas schiefgelaufen.
Direkte Verbindung zum Backend testen, um die rohen HTTP-Headers zu sehen:
# Direkt gegen die EC2-Instanz testen (Security Group muss temporären Zugriff erlauben)
curl -v http://10.0.1.50:8080/api/endpoint 2>&1 | grep -E '^[<>]'
Auf folgende Probleme in den Response-Headers achten:
- Header-Namen mit Leerzeichen oder Sonderzeichen
- Mehrfach gesetzte
Content-LengthHeader mit unterschiedlichen Werten - Ungültige
Transfer-EncodingWerte - Header-Werte mit Steuerzeichen (CR, LF innerhalb eines Header-Werts)
Schritt 6: CloudWatch Metriken für Muster-Erkennung
Bevor einzelne Requests manuell analysiert werden, lohnt es sich, die zeitliche Verteilung der 502-Fehler zu verstehen. Treten sie gleichmäßig auf, oder gibt es Spitzen? Korrelieren sie mit bestimmten Zeiten oder Deployments?
aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name HTTPCode_ELB_5XX_Count \
--dimensions Name=LoadBalancer,Value=app/mein-alb/1234567890abcdef \
--start-time 2024-01-15T00:00:00Z \
--end-time 2024-01-15T23:59:59Z \
--period 300 \
--statistics Sum
Zusätzlich die Metrik HTTPCode_Target_5XX_Count prüfen. Wenn HTTPCode_ELB_5XX_Count hoch ist, aber HTTPCode_Target_5XX_Count niedrig oder null, kommen die 502-Fehler vom ALB selbst – das Backend antwortet gar nicht oder mit ungültigen Responses.
aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name HTTPCode_Target_5XX_Count \
--dimensions Name=LoadBalancer,Value=app/mein-alb/1234567890abcdef \
--start-time 2024-01-15T00:00:00Z \
--end-time 2024-01-15T23:59:59Z \
--period 300 \
--statistics Sum
Erfahrung aus der Praxis: Der 502 der erst nach Deployment auftrat
Symptom: Nach einem Deployment begannen sporadische 502-Fehler, etwa 2-3% aller Requests. Die Instanzen waren 'Healthy', das Application-Log zeigte keine Fehler, und die 502-Fehler traten ohne erkennbares Muster auf.
Erste Annahme: Das neue Deployment hatte einen Bug eingeführt, der bestimmte Requests zum Absturz brachte. Also: Rollback. Die 502-Fehler blieben.
Tatsächliche Ursache: Das Deployment hatte die nginx-Konfiguration geändert – konkret den keepalive_timeout von 75 auf 30 Sekunden reduziert, um 'Ressourcen zu sparen'. Der ALB Idle Timeout war auf 60 Sekunden konfiguriert. Nginx schloss Verbindungen nach 30 Sekunden, der ALB hielt sie für weitere 30 Sekunden offen und sendete neue Requests auf bereits geschlossenen Verbindungen.
Der Beweis im Access Log: target_status_code war in allen 502-Einträgen ein Bindestrich. Kein Backend-Fehler – die Verbindung war einfach schon zu.
Fix: keepalive_timeout in nginx auf 75 Sekunden zurückgesetzt (höher als der ALB Idle Timeout von 60 Sekunden). 502-Fehler verschwanden sofort.
Das Lehrreiche daran: Der Rollback hat nicht geholfen, weil das Problem nicht im Anwendungscode lag, sondern in der Webserver-Konfiguration, die separat deployed wurde. Wer nur den App-Code zurückrollt, lässt die nginx-Config unberührt.
IAM-Berechtigungen für die Diagnose
Für die oben beschriebenen CLI-Befehle sind folgende Mindestberechtigungen erforderlich:
🔽 IAM Policy für ALB 502-Diagnose anzeigen
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ALBReadAccess",
"Effect": "Allow",
"Action": [
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth"
],
"Resource": "*"
},
{
"Sid": "ALBModifyAttributes",
"Effect": "Allow",
"Action": [
"elasticloadbalancing:ModifyLoadBalancerAttributes"
],
"Resource": "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/mein-alb/1234567890abcdef"
},
{
"Sid": "CloudWatchReadAccess",
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricStatistics"
],
"Resource": "*"
},
{
"Sid": "S3LogAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::mein-alb-logs-bucket",
"arn:aws:s3:::mein-alb-logs-bucket/*"
]
}
]
}
Hinweis: DescribeLoadBalancers und cloudwatch:GetMetricStatistics erfordern "Resource": "*", da diese Aktionen keine ressourcenspezifische Einschränkung unterstützen. Immer die aktuelle Service Authorization Reference prüfen, bevor ARN-Einschränkungen angewendet werden.
ALB 502 Bad Gateway – Zusammenfassung und nächste Schritte
Ein 502 vom ALB bei gleichzeitig 'Healthy' angezeigten Targets ist kein Widerspruch – Health Checks und Request-Handling sind vollständig getrennte Verbindungen. Die Diagnose beginnt immer mit den ALB Access Logs und dem Feld target_status_code: Bindestrich bedeutet Verbindungsabbruch, ein vorhandener Statuscode bedeutet ungültige HTTP-Antwort.
Die häufigste Ursache in produktiven Umgebungen ist der Keep-Alive Timeout Mismatch – der App-Server-Timeout muss immer größer sein als der ALB Idle Timeout. Das ist keine offensichtliche Abhängigkeit, und sie bricht regelmäßig nach Konfigurationsänderungen, die scheinbar nichts mit dem Load Balancer zu tun haben.
Weiterführende Ressourcen:
- AWS Dokumentation: ALB Access Logs
- AWS Dokumentation: ALB Troubleshooting
- AWS Knowledge Center: ALB 502 Fehler beheben
Glossar
| Begriff | Bedeutung |
|---|---|
| 502 Bad Gateway | HTTP-Statuscode: Der Proxy (ALB) hat vom Backend eine ungültige Antwort erhalten oder gar keine Antwort bekommen. |
| Keep-Alive Timeout | Zeitspanne, nach der ein HTTP-Server eine persistente Verbindung ohne Aktivität schließt. Muss beim App-Server größer sein als der ALB Idle Timeout. |
| target_status_code | Feld im ALB Access Log: HTTP-Statuscode der Backend-Antwort. Ein Bindestrich (-) bedeutet, dass keine Antwort empfangen wurde. |
| ALB Idle Timeout | Konfigurierbare Zeitspanne (Standard: 60 Sekunden), nach der der ALB eine inaktive Verbindung schließt. |
| Protokoll-Mismatch | Konfigurationsfehler, bei dem Target Group und App-Server unterschiedliche Protokolle (HTTP vs. HTTPS) verwenden. |
Kommentare
Kommentar veröffentlichen