{"id":49374,"date":"2025-05-19T02:13:31","date_gmt":"2025-05-19T02:13:31","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=49374"},"modified":"2025-05-19T02:13:31","modified_gmt":"2025-05-19T02:13:31","slug":"grpc-aws-alb-downgrades-http-2-%e2%86%92-http-1-1","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/grpc-aws-alb-downgrades-http-2-%e2%86%92-http-1-1\/","title":{"rendered":"GRPC: AWS ALB \u201cdowngrades HTTP\/2 \u2192 HTTP\/1.1"},"content":{"rendered":"\n<p>Excellent question. When we say <strong>AWS ALB \u201cdowngrades HTTP\/2 \u2192 HTTP\/1.1\u201d<\/strong>, it means that although the client connects to ALB over <strong>HTTP\/2<\/strong>, ALB then connects to your backend (e.g., EKS service or pod) using <strong>HTTP\/1.1<\/strong>.<\/p>\n\n\n\n<p>This causes <strong>loss of several core gRPC features<\/strong>, because gRPC <strong>requires HTTP\/2 end-to-end<\/strong> to function properly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcc9 What\u2019s Lost During the Downgrade (HTTP\/2 \u2192 HTTP\/1.1)<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>gRPC Feature<\/th><th>Lost or Broken?<\/th><th>Why It Matters<\/th><\/tr><\/thead><tbody><tr><td><strong>Streaming (client\/server\/bidi)<\/strong><\/td><td>\u274c Lost<\/td><td>HTTP\/1.1 can\u2019t multiplex or maintain bidirectional streams<\/td><\/tr><tr><td><strong>HTTP\/2 Trailers<\/strong><\/td><td>\u274c Lost<\/td><td>gRPC uses trailers for <code>grpc-status<\/code>, <code>grpc-message<\/code> \u2192 clients can\u2019t detect errors correctly<\/td><\/tr><tr><td><strong>Multiplexing<\/strong><\/td><td>\u274c Lost<\/td><td>HTTP\/2 can handle multiple streams over one connection, HTTP\/1.1 cannot<\/td><\/tr><tr><td><strong>Header Compression (HPACK)<\/strong><\/td><td>\u274c Lost<\/td><td>Increases payload size and latency<\/td><\/tr><tr><td><strong>Flow Control per Stream<\/strong><\/td><td>\u274c Lost<\/td><td>HTTP\/1.1 has global flow control \u2192 less efficient<\/td><\/tr><tr><td><strong>Binary Framing<\/strong><\/td><td>\u26a0\ufe0f May break<\/td><td>gRPC uses strict framing over HTTP\/2 \u2192 framing can get corrupted<\/td><\/tr><tr><td><strong>gRPC Metadata Headers<\/strong><\/td><td>\u26a0\ufe0f Incomplete<\/td><td>Custom headers like <code>grpc-timeout<\/code> may be stripped or altered<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 What Gets Passed (But Not Fully gRPC-Compatible)<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Element<\/th><th>Passed by ALB?<\/th><th>Note<\/th><\/tr><\/thead><tbody><tr><td><strong>HTTP POST<\/strong><\/td><td>\u2705 Yes<\/td><td>gRPC uses POST method<\/td><\/tr><tr><td><strong>Content-Type<\/strong><\/td><td>\u2705 Yes<\/td><td><code>application\/grpc<\/code> usually retained<\/td><\/tr><tr><td><strong>Basic Protobuf Payload<\/strong><\/td><td>\u26a0\ufe0f Partially<\/td><td>Still sent, but streaming\/chunking could corrupt it<\/td><\/tr><tr><td><strong>TLS Termination<\/strong><\/td><td>\u2705 Yes<\/td><td>ALB can terminate TLS and forward plaintext internally<\/td><\/tr><tr><td><strong>Status Code (e.g., 200, 500)<\/strong><\/td><td>\u2705 Basic<\/td><td>But not the gRPC-specific status like <code>grpc-status<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 What Happens in Practice?<\/h2>\n\n\n\n<p>A native gRPC client (e.g., Go, Python, Node) expects:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Streaming support<\/li>\n\n\n\n<li><code>grpc-status<\/code> and <code>grpc-message<\/code> in HTTP\/2 trailers<\/li>\n\n\n\n<li>Correct gRPC error handling<\/li>\n<\/ul>\n\n\n\n<p>But with ALB in the path:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Streaming <strong>breaks<\/strong> \u2014 long-lived calls are prematurely closed<\/li>\n\n\n\n<li>Status trailers are <strong>lost<\/strong> \u2014 client receives only <code>200 OK<\/code> even on failure<\/li>\n\n\n\n<li>Interceptors and retry logic in gRPC client <strong>fail silently<\/strong><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd0e Example of Broken gRPC Call via ALB<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Expected gRPC Response (over HTTP\/2):<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">:status: 200\ncontent-type: application\/grpc\ngrpc-status: 5\ngrpc-message: Not Found\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">ALB Response (after downgrade):<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">HTTP\/1.1 200 OK\ncontent-type: application\/grpc\n(no grpc-status trailer!)\n<\/code><\/span><\/pre>\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Result: gRPC client sees it as success (<code>OK<\/code>), even though the call failed on the server.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 Summary Table<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Feature<\/th><th>HTTP\/2 Required<\/th><th>Passed via ALB (HTTP\/1.1)<\/th><th>Outcome<\/th><\/tr><\/thead><tbody><tr><td>Unary gRPC Calls<\/td><td>\u274c Optional<\/td><td>\u2705 Yes<\/td><td>May work<\/td><\/tr><tr><td>gRPC Streaming (all types)<\/td><td>\u2705 Yes<\/td><td>\u274c No<\/td><td>Broken<\/td><\/tr><tr><td>gRPC Trailers (<code>grpc-status<\/code>)<\/td><td>\u2705 Yes<\/td><td>\u274c No<\/td><td>Broken<\/td><\/tr><tr><td>Metadata headers<\/td><td>\u26a0\ufe0f Often required<\/td><td>\u26a0\ufe0f Incomplete<\/td><td>Unpredictable<\/td><\/tr><tr><td>Observability (otel\/metrics)<\/td><td>\u2705 Yes<\/td><td>\u274c Incomplete<\/td><td>Partial \/ broken<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd27 How to Prevent This<\/h2>\n\n\n\n<p>Use <strong>NLB + Envoy Gateway<\/strong> or a gRPC-aware ingress like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Envoy Gateway with HTTP\/2<\/li>\n\n\n\n<li>Istio IngressGateway<\/li>\n\n\n\n<li>Gateway API with <code>GRPCRoute<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Excellent question. When we say AWS ALB \u201cdowngrades HTTP\/2 \u2192 HTTP\/1.1\u201d, it means that although the client connects to ALB over HTTP\/2, ALB then connects to your backend (e.g., EKS&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[2],"tags":[],"class_list":["post-49374","post","type-post","status-publish","format-standard","hentry","category-uncategorised"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49374","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=49374"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49374\/revisions"}],"predecessor-version":[{"id":49375,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49374\/revisions\/49375"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=49374"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=49374"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=49374"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}