Skip to content

HAProxy 3.x TLS 1.3 0-RTT Broken? #3270

@cmason3

Description

@cmason3

Detailed Description of the Problem

I am currently tracking the latest haproxy release (v3.3.4) via docker.io/haproxytech/haproxy-alpine. I always perform a Qualys scan via https://ssllabs.com/ssltest/analyze.html after every upgrade to check everything is still working, but I have noticed 0-RTT is being reported as "No" even though I have 'allow-0rtt' on the bind line.

I thought this was an issue with SSL Labs as they have failed to report this correctly in the past, but I decided to verify this using SSLyze v6.3.0 and it looks like it is actually broken - here are my results from the latest releases:

v3.4-dev4

  • TLS 1.3 Early Data: Not Supported

v3.3.4

  • TLS 1.3 Early Data: Not Supported

v3.2.13

  • TLS 1.3 Early Data: Not Supported

v3.1.15

  • TLS 1.3 Early Data: Not Supported

v3.0.17

  • TLS 1.3 Early Data: Not Supported

v2.8.18

  • TLS 1.3 Early Data: Suppported - Server accepted early data

It looks like 0-RTT worked in 2.8.18, but somewhere between 2.8.18 and 3.0.17 it broke.

Expected Behavior

I think if I configure allow-0rtt then it should work :)

Steps to Reproduce the Behavior

Deploy haproxy v3.3.4 with the attached configuration and use the following to verify 0-RTT:

python3 -m sslyze --early_data <hostname>

Do you have any idea what may have caused this?

No response

Do you have an idea how to solve the issue?

No response

What is your configuration?

global
  log stdout format raw local0 notice
  maxconn 4096
  user root

  tune.listener.default-shards by-thread
  ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
  ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256
  ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
  ssl-default-bind-curves X25519:P-256

defaults
  log global
  option redispatch
  option splice-auto
  option socket-stats
  option log-health-checks
  option http-server-close
  retries 3
  timeout http-request 10s
  timeout queue 1m
  timeout connect 10s
  timeout client 1m
  timeout server 1m
  timeout http-keep-alive 10s
  timeout check 3s
  mode http

frontend fe_http
  bind ipv4@:80 name ipv4
  redirect scheme https code 301

frontend fe_https
  bind ipv4@:443 ssl crt /usr/local/etc/haproxy/fullchain.pem alpn h2,http/1.1 allow-0rtt tfo name ipv4
  bind quic4@:443 ssl crt /usr/local/etc/haproxy/fullchain.pem alpn h3 allow-0rtt name quic4
  http-response set-header Strict-Transport-Security 'max-age=63072000; includeSubDomains; preload'
  http-response set-header Alt-Svc 'h3=":443"; ma=86400'
  default_backend be_http

backend be_http
  option httpchk GET /ping
  http-check expect string OK
  server default 127.0.0.1:8080 check

Output of haproxy -vv

HAProxy version 3.3.4-c2bffae0a 2026/02/19 - https://haproxy.org/
Status: stable branch - will stop receiving fixes around Q1 2027.
Known bugs: http://www.haproxy.org/bugs/bugs-3.3.4.html
Running on: Linux 5.14.0-624.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Oct 9 16:40:07 UTC 2025 x86_64
Build options : 
  TARGET  = linux-musl
  CC      = cc
  CFLAGS  = -O2 -g -fwrapv -fvect-cost-model=very-cheap
  OPTIONS = USE_PTHREAD_EMULATION=1 USE_LINUX_TPROXY=1 USE_GETADDRINFO=1 USE_OPENSSL_AWSLC=1 USE_LUA=1 USE_SLZ=1 USE_TFO=1 USE_QUIC=1 USE_PROMEX=1 USE_PCRE2=1 USE_PCRE2_JIT=1
  DEBUG   = 

Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ECH -ENGINE +EPOLL -EVPORTS +GETADDRINFO -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 -WURFL -ZLIB +ACME +HAVE_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.65.1)
Running on SSL library version : AWS-LC 1.65.1
SSL library supports TLS extensions : yes
SSL library supports SNI : yes
SSL library FIPS mode : no
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.6
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.43 2024-02-16
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 13.2.1 20240309

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
         h2 : mode=HTTP  side=FE|BE  mux=H2    flags=HTX|HOL_RISK|NO_UPG
  <default> : mode=HTTP  side=FE|BE  mux=H1    flags=HTX
         h1 : mode=HTTP  side=FE|BE  mux=H1    flags=HTX|NO_UPG
       fcgi : mode=HTTP  side=BE     mux=FCGI  flags=HTX|HOL_RISK|NO_UPG
  <default> : mode=SPOP  side=BE     mux=SPOP  flags=HOL_RISK|NO_UPG
       spop : mode=SPOP  side=BE     mux=SPOP  flags=HOL_RISK|NO_UPG
  <default> : mode=TCP   side=FE|BE  mux=PASS  flags=
       none : mode=TCP   side=FE|BE  mux=PASS  flags=NO_UPG

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

Last Outputs and Backtraces


Additional Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    status: needs-triageThis issue needs to be triaged.type: bugThis issue describes a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions