Skip to content

Unexpected behavior occurred in h2c requests when using the h2 backend. #3397

@zhanhb

Description

@zhanhb

Detailed Description of the Problem

When the backend is h2, h2c requests is not processed correctly.

Expected Behavior

When the frontend is marked with proto h1, a 200 response should be returned;

If the frontend is not marked as "proto", it is best to return 101 and upgrade the connection to handle binary bodies;

I'm not sure what response should be generated if I send an H2C request to the frontend, and when the frontend is marked with proto h2.

Steps to Reproduce the Behavior

  1. haproxy -f h2c.cfg
  2. curl --http2 http://127.0.0.1:8082 -i
  3. curl --http2 http://127.0.0.1:8083 -i

Do you have any idea what may have caused this?

It seemd the commit 8dd49df is to blame.

Do you have an idea how to solve the issue?

No response

What is your configuration?

# h2c.cfg
defaults
    mode http

    timeout client 1s
    timeout connect 1s
    timeout server 1s

    log stderr local0
    option httplog

listen step1
    bind 127.0.0.1:8082
    server _ 127.0.0.1:10002 proto h2

listen step1_h1
    bind 127.0.0.1:8083 proto h1
    server _ 127.0.0.1:10002 proto h2

listen step1_h2
    bind 127.0.0.1:8084 proto h2
    server _ 127.0.0.1:10002 proto h2

listen step2
    bind 127.0.0.1:10002 proto h2
    server _ 127.0.0.1:10003 proto h1

frontend step3
    bind 127.0.0.1:10003 proto h1
    http-request return content-type text/plain string "hello, world!\n"

Output of haproxy -vv

HAProxy version 3.4-dev13-e8c9aa-39 2026/05/25 - https://haproxy.org/
Status: development branch - not safe for use in production.
Known bugs: https://github.com/haproxy/haproxy/issues?q=is:issue+is:open
Running on: Linux 7.0.9-205.fc44.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 21 16:31:48 UTC 2026 x86_64
Build options : 
  TARGET  = linux-glibc
  CC      = clang
  CFLAGS  = -O2 -g -fwrapv -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -flto=auto -ffat-lto-objects -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security
  OPTIONS = USE_OPENSSL_AWSLC=1 USE_LUA=1 USE_QUIC=1 USE_PROMEX=1 USE_PCRE2=1 USE_PCRE2_JIT=1
  DEBUG   = 

Feature list : -51DEGREES +ACCEPT4 +ACME +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ECH -ENGINE +EPOLL -EVPORTS +GETADDRINFO +HAVE_TCP_MD5SIG -KQUEUE +KTLS -LIBATOMIC +LIBCRYPT +LINUX_CAP +LINUX_SPLICE +LINUX_TPROXY +LUA +MATH -MEMORY_PROFILING +NETFILTER +NS -OBSOLETE_LINKER +OPENSSL +OPENSSL_AWSLC -OPENSSL_WOLFSSL -OT -PCRE +PCRE2 +PCRE2_JIT -PCRE_JIT +POLL +PRCTL -PROCCTL +PROMEX -PTHREAD_EMULATION +QUIC -QUIC_OPENSSL_COMPAT +RT +SHM_OPEN +SLZ +SSL -STATIC_PCRE -STATIC_PCRE2 +TFO +THREAD +THREAD_DUMP +TPROXY +TRACE -WURFL -ZLIB
Detected feature list : +HAVE_WORKING_TCP_MD5SIG

Default settings :
  bufsize = 16384, maxrewrite = 1024, maxpollevents = 200

Built with multi-threading support (MAX_TGROUPS=32, MAX_THREADS=1024, default=2).
Built with SSL library version : OpenSSL 1.1.1 (compatible; AWS-LC 1.73.0)
Running on SSL library version : AWS-LC 1.73.0
SSL library supports TLS extensions : yes
SSL library supports SNI : yes
SSL library FIPS mode : no
SSL library default verify directory : /etc/ssl/certs
SSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
QUIC: connection sock-per-conn mode support : yes
QUIC: GSO emission support : yes
Built with Lua version : Lua 5.4.8
Built with the Prometheus exporter as a service
Built with network namespace support.
Built with libslz for stateless compression.
Compression algorithms supported : identity("identity"), deflate("deflate"), raw-deflate("deflate"), gzip("gzip")
Built with transparent proxy support using: IP_TRANSPARENT IPV6_TRANSPARENT IP_FREEBIND
Built with PCRE2 version : 10.47 2025-10-21
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with clang compiler version 22.1.5 (Fedora 22.1.5-1.fc44)

Available polling systems :
      epoll : pref=300,  test result OK
       poll : pref=200,  test result OK
     select : pref=150,  test result OK
Total: 3 (3 usable), will use epoll.

Available multiplexer protocols :
(protocols marked as <default> cannot be specified using 'proto' keyword)
       quic : mode=HTTP  side=FE|BE  mux=QUIC  flags=HTX|NO_UPG|FRAMED
       qmux : mode=HTTP  side=FE|BE  mux=QMUX  flags=HTX|NO_UPG
         h2 : mode=HTTP  side=FE|BE  mux=H2    flags=HTX|HOL_RISK|NO_UPG
         h1 : mode=HTTP  side=FE|BE  mux=H1    flags=HTX|NO_UPG
  <default> : mode=HTTP  side=FE|BE  mux=H1    flags=HTX
       fcgi : mode=HTTP  side=BE     mux=FCGI  flags=HTX|HOL_RISK|NO_UPG
       spop : mode=SPOP  side=BE     mux=SPOP  flags=HOL_RISK|NO_UPG
  <default> : mode=SPOP  side=BE     mux=SPOP  flags=HOL_RISK|NO_UPG
       none : mode=TCP   side=FE|BE  mux=PASS  flags=NO_UPG
  <default> : mode=TCP   side=FE|BE  mux=PASS  flags=

Available services : prometheus-exporter
Available filters :
        [BWLIM] bwlim-in
        [BWLIM] bwlim-out
        [CACHE] cache
        [COMP] comp-req
        [COMP] comp-res
        [COMP] compression
        [FCGI] fcgi-app
        [SPOE] spoe
        [TRACE] trace

Last Outputs and Backtraces

[NOTICE]   (266210) : Automatically setting global.maxconn to 262131.
<134>May 26 12:04:44 haproxy[266210]: 127.0.0.1:39850 [26/May/2026:12:04:44.326] step3 step3/<NOSRV> 0/-1/-1/-1/0 200 0 - - LR-- 3/1/0/0/0 0/0 "GET / HTTP/1.1"
<134>May 26 12:04:44 haproxy[266210]: 127.0.0.1:57902 [26/May/2026:12:04:44.325] step2 step2/_ 0/0/1/0/1 200 86 - - ---- 3/1/0/0/0 0/0 "GET https://127.0.0.1:8082/ HTTP/2.0"
<134>May 26 12:04:44 haproxy[266210]: 127.0.0.1:41180 [26/May/2026:12:04:44.324] step1 step1/_ 0/0/1/1/2 101 108 - - SD-- 3/1/0/0/0 0/0 "GET / HTTP/1.1"
<134>May 26 12:04:49 haproxy[266210]: 127.0.0.1:39864 [26/May/2026:12:04:49.668] step3 step3/<NOSRV> 0/-1/-1/-1/0 200 0 - - LR-- 3/1/0/0/0 0/0 "GET / HTTP/1.1"
<134>May 26 12:04:49 haproxy[266210]: 127.0.0.1:57910 [26/May/2026:12:04:49.667] step2 step2/_ 0/0/1/0/1 200 86 - - ---- 3/1/0/0/0 0/0 "GET https://127.0.0.1:8083/ HTTP/2.0"
<134>May 26 12:04:49 haproxy[266210]: 127.0.0.1:43096 [26/May/2026:12:04:49.666] step1_h1 step1_h1/_ 0/0/1/1/2 101 108 - - SD-- 3/1/0/0/0 0/0 "GET / HTTP/1.1"




HTTP/1.1 101 Switching Protocols
content-type: text/plain
content-length: 14
connection: upgrade

curl: (16) Remote peer returned unexpected data while we expected SETTINGS frame.  Perhaps, peer does not support HTTP/2 properly.

Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.4This issue affects the HAProxy 2.4 stable branch.2.6This issue affects the HAProxy 2.6 stable branch.2.8This issue affects the HAProxy 2.8 stable branch.3.0This issue affects the HAProxy 3.0 stable branch.3.2The issue affects the HAProxy 3.2 stable branch.3.3The issue affects the HAProxy 3.3 stable branch.status: fixedThis issue is a now-fixed bug.type: bugThis issue describes a bug.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions