Problem: HAProxy “multimodal alignment” errors in a mixed‑protocol Kubernetes ingress
In a GKE‑based AI platform the same HAProxy ingress is used to expose:
- TensorFlow Serving (gRPC over HTTP/2)
- FastAPI inference endpoints (HTTP/1.1)
- WebSocket streams (HTTP/1.1 upgrade)
After a rolling update the following symptoms appeared:
[WARNING] 2026/07/08 10:12:45 HA-Proxy[WARN] : content mismatch, header size exceeds payload
[ERROR] 2026/07/08 10:12:46 HA-Proxy[ERR] : HTTP/2: malformed request line
[ALERT] 2026/07/08 10:12:47 HA-Proxy[ALERT]: invalid HTTP/2 frame received
[ERROR] 2026/07/08 10:12:48 HA-Proxy[ERR] : HTTP/2: stream error: PROTOCOL_ERROR
Resulting impact:
- 502/503 responses for gRPC inference calls
- Health‑check probes (HTTP/1.1) falsely reported backend failure, causing pod restarts
- Intermittent “header size exceeds payload” errors during peak load for WebSocket streams
Root Cause Analysis
HAProxy parses inbound traffic according to the mode of the frontend. When a single frontend is configured with mode http and receives both HTTP/1.1 and HTTP/2 (gRPC) streams, HAProxy must decide which protocol parser to apply. The decision is driven by:
- ALPN negotiation during TLS hand‑shake (HAProxy docs: “gRPC support – HTTP/2 handling, ALPN”)
- Presence of the
Content-Type: application/grpcheader (content switching rules)
In the incident the frontend reused the same TCP connection for both protocols because:
- ALPN was not forced; some clients fell back to HTTP/1.1 while others used HTTP/2.
- Content‑switching rules based on
:pathor:authoritywere missing, so HAProxy fell back to the default backend after the first request on the connection. - A bug in HAProxy 2.8 (see GitHub issue #8453) caused the HTTP/2 parser state to become corrupted when an HTTP/1.1 request arrived on a connection that had already been upgraded to HTTP/2.
When the parser state diverged, HAProxy attempted to interpret an HTTP/1.1 payload as an HTTP/2 frame, leading to the “invalid HTTP/2 frame” and “header size exceeds payload” messages – the classic “multimodal alignment” failure described in the HAProxy Enterprise “Multimodal Alignment” troubleshooting chapter.
Investigation and Debugging Steps
1. Capture HAProxy logs with full request details
docker exec -it haproxy \
/bin/sh -c "cat /var/log/haproxy.log | grep -E 'ERR|WARN|ALERT'"
2. Verify TLS ALPN negotiation
openssl s_client -connect ingress.example.com:443 -alpn h2 -servername ingress.example.com
# Expected output includes "ALPN protocol: h2"
3. Inspect active connections
echo "show sess" | socat stdio /var/run/haproxy.sock
# Look for sessions with "proto=HTTP/2" followed by "proto=HTTP/1.1"
4. Reproduce with curl and grpcurl
# HTTP/1.1 request (FastAPI)
curl -v http://ingress.example.com/v1/predict
# gRPC request (TensorFlow Serving)
grpcurl -plaintext -proto tensorflow.proto \
ingress.example.com:80 tensorflow.serving.PredictionService/Predict
5. Check frontend configuration
frontend ai-ingress
bind *:443 ssl crt /etc/haproxy/certs/ingress.pem alpn h2,http/1.1
mode http
option http-use-htx
# Existing rule (problematic)
default_backend http-backend
6. Review content‑switching rules from the HAProxy Kubernetes Ingress Controller guide
The guide recommends separate use_backend clauses that inspect :path or :authority to route gRPC vs. HTTP/1.1 traffic.
Solution: Separate Frontends or Explicit ALPN‑Based Routing
Two proven approaches:
Approach A – Split frontends by protocol
Define one frontend that only accepts HTTP/2 (gRPC) and another that only accepts HTTP/1.1. This eliminates any state sharing on a single TCP connection.
# Frontend for gRPC (HTTP/2)
frontend grpc-in
bind *:8443 ssl crt /etc/haproxy/certs/ingress.pem alpn h2
mode http
option http-use-htx
use_backend tf-serving-backend if { req.hdr(Content-Type) -i application/grpc }
default_backend grpc-404
# Frontend for REST/WebSocket (HTTP/1.1)
frontend http-in
bind *:8080 ssl crt /etc/haproxy/certs/ingress.pem alpn http/1.1
mode http
option http-use-htx
acl is_ws hdr(Upgrade) -i websocket
use_backend fastapi-backend if { path_beg /v1/ }
use_backend websocket-backend if is_ws
default_backend http-404
Update the Kubernetes Service to expose both ports (8443 and 8080) and adjust the Ingress spec.rules accordingly.
Approach B – Single frontend with ALPN‑based content switching
If exposing a single port is mandatory, add explicit use_backend rules that test the negotiated protocol via the HAProxy variable ssl_fc_alpn (available since HAProxy 2.4).
frontend ai-ingress
bind *:443 ssl crt /etc/haproxy/certs/ingress.pem alpn h2,http/1.1
mode http
option http-use-htx
# Route gRPC when ALPN is h2 and Content-Type is gRPC
acl is_grpc ssl_fc_alpn h2
acl grpc_ct hdr(Content-Type) -i application/grpc
use_backend tf-serving-backend if is_grpc grpc_ct
# Route HTTP/1.1 REST endpoints
acl is_rest path_beg /v1/
use_backend fastapi-backend if is_rest
# Route WebSocket upgrades
acl is_ws hdr(Upgrade) -i websocket
use_backend websocket-backend if is_ws
default_backend http-404
Key changes compared to the original configuration:
- Added
alpn h2,http/1.1on the bind line (ensures client‑side ALPN is advertised). - Enabled
option http-use-htxto force HAProxy to use the new HTX parsing engine, which is required for reliable HTTP/2 handling (HAProxy docs: “Mode http” and “option http-use-htx”). - Introduced explicit
aclchecks forssl_fc_alpnandContent-Typeto prevent the parser from mixing protocols on the same connection.
Verification
Functional tests
# Verify gRPC endpoint
grpcurl -proto tensorflow.proto -insecure \
ingress.example.com:443 tensorflow.serving.PredictionService/Predict
# Expected: SUCCESS response, no 502/503
# Verify REST endpoint
curl -k https://ingress.example.com/v1/predict
# Expected: 200 OK with JSON payload
# Verify WebSocket upgrade
websocat -n -t wss://ingress.example.com/ws/stream
# Expected: connection established, no “header size exceeds payload”
Log inspection
grep -E 'ERR|WARN|ALERT' /var/log/haproxy.log
# Should return no entries related to “invalid HTTP/2 frame” or “header size exceeds payload”
Metrics
Prometheus HAProxy exporter should show zero increase in haproxy_frontend_http_response_errors_total for the affected frontends.
Prevention and Operational Best Practices
- Enforce ALPN negotiation: always include both
h2andhttp/1.1in thebindline; reject connections that do not present ALPN. - Use separate frontends for divergent protocols when the traffic mix is heavy or when latency‑sensitive services (e.g., video streaming) share the same ingress.
- Enable
option http-use-htxon all HTTP frontends to leverage HAProxy’s modern HTTP/2 parser. - Monitor HAProxy alerts for the specific error strings listed in the “Common errors” evidence set; set up alerting on
haproxy_frontend_http_response_errors_total. - Run integration tests in CI that simulate concurrent HTTP/1.1 and gRPC calls on the same port, asserting that no “multimodal alignment” log entries appear.
- Pin HAProxy version to a release that includes the fix for issue #8453 (≥ 2.8.2) or apply the back‑ported patch if using an older version.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does HAProxy sometimes treat an HTTP/1.1 request as HTTP/2?
Because the frontend is configured inmode httpwithout explicit ALPN routing. If the first request on a TCP connection negotiates HTTP/2, HAProxy keeps the HTTP/2 parser state for the whole connection. A subsequent HTTP/1.1 request on the same socket is then parsed as HTTP/2, producing “malformed request line” errors. - Can I keep a single port and still avoid multimodal alignment errors?
Yes, by using ALPN‑based ACLs (ssl_fc_alpn) andoption http-use-htxto force HAProxy to switch parsers per request. The “single‑frontend” solution shown in Approach B demonstrates this. - Do health‑check probes need special handling?
Health probes are HTTP/1.1 by default. Ensure they are routed to an HTTP/1.1 backend (e.g., a dedicatedhealth‑checkbackend) or add an ACL that forcesuse_backend health‑check-backend if { path /health }. This prevents a probe from hitting a gRPC backend and causing a 503. - Is
option http-use-htxmandatory for gRPC?
Starting with HAProxy 2.4 it is recommended for any HTTP/2 traffic. It enables the HTX transaction engine, which correctly separates HTTP/2 frames from HTTP/1.1 payloads and eliminates the alignment issue described in the HAProxy Enterprise “Multimodal Alignment” chapter. - What version of HAProxy contains the fix for the parser state bug?
The bug reported in GitHub issue #8453 was fixed in HAProxy 2.8.2. If you cannot upgrade, back‑port the patch from the HAProxy repository or isolate protocols using separate frontends.