-
Notifications
You must be signed in to change notification settings - Fork 913
HAProxy 3.x TLS 1.3 0-RTT Broken? #3270
Description
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 checkOutput 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