Haproxy 3.0.6 - Backend ssl handshake failure

I am just trying out simple haproxy configuration in http mode where i want https connection between client and haproxy as well as between haproxy and my backend server.
I am having this issue of ssl handshake failure between haproxy and backend server and can’t quite figure it out what is wrong with the configuration. I am running haproxy on my docker container.
This is my haproxy -vv

HAProxy version 3.0.6-c2c0090 2024/11/07 - https://haproxy.org/
Status: long-term supported branch - will stop receiving fixes around Q2 2029.
Known bugs: http://www.haproxy.org/bugs/bugs-3.0.6.html
Running on: Linux 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64
Build options :
  TARGET  = linux-glibc
  CC      = cc
  CFLAGS  = -O2 -g -fwrapv
  OPTIONS = USE_GETADDRINFO=1 USE_OPENSSL=1 USE_LUA=1 USE_PROMEX=1 USE_PCRE2=1 USE_PCRE2_JIT=1
  DEBUG   = 

Feature list : -51DEGREES +ACCEPT4 +BACKTRACE -CLOSEFROM +CPU_AFFINITY +CRYPT_H -DEVICEATLAS +DL -ENGINE +EPOLL -EVPORTS +GETADDRINFO -KQUEUE -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 +SYSTEMD +TFO +THREAD +THREAD_DUMP +TPROXY -WURFL -ZLIB

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

Built with multi-threading support (MAX_TGROUPS=16, MAX_THREADS=256, default=4).
Built with OpenSSL version : OpenSSL 3.0.15 3 Sep 2024
Running on OpenSSL version : OpenSSL 3.0.15 3 Sep 2024
OpenSSL library supports TLS extensions : yes
OpenSSL library supports SNI : yes
OpenSSL library supports : TLSv1.0 TLSv1.1 TLSv1.2 TLSv1.3
OpenSSL providers loaded : default
Built with Lua version : Lua 5.4.4
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.42 2022-12-11
PCRE2 library supports JIT : yes
Encrypted password support via crypt(3): yes
Built with gcc compiler version 12.2.0

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)
         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=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

The below is my haproxy configuration

# haproxy.cfg
global
    stats socket /var/lib/haproxy/stats
    stats socket *:1999 level admin 
    server-state-file /usr/local/etc/haproxy/haproxy.state
    log stdout format raw local0 debug
    maxconn 4096

defaults
    log global
    mode http
    option httplog
    option dontlognull
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    load-server-state-from-file global

frontend prometheus
  bind *:8405
  mode http
  http-request use-service prometheus-exporter
  no log

frontend http_front
    bind *:80
    bind *:443 ssl crt /run/secrets/ssl_cert

    acl path_fact path_beg /fact

    # deny request on missing auth header 
    http-request deny content-type 'text/html' string 'Missing Authorization HTTP header' if !{ req.hdr(authorization) -m found } !path_chat !path_fact 

    # variables
    http-request set-var(txn.alg) http_auth_bearer,jwt_header_query('$.alg')    # get header part of the JWT
    http-request set-var(txn.exp) http_auth_bearer,jwt_payload_query('$.exp','int') # get payload part of the JWT
    http-request set-var(txn.now) date() # get current date

    # check if token is expired
    http-request deny content-type 'text/html' string 'JWT has expired' if { var(txn.exp),sub(txn.now) -m int lt 0 }

    # acl for path match
    acl path_httpbin path_beg /httpbin
    acl jwt_valid_key_httpbin http_auth_bearer,jwt_verify(txn.alg,"/run/secrets/public_key_three") -m int 1
    http-request deny content-type 'text/html' string 'Invalid JWT signature for httpbin' if path_httpbin !jwt_valid_key_httpbin

    use_backend cat_fact if path_fact
    use_backend httpbin_backend if path_httpbin

    default_backend be_test

backend httpbin_backend
    http-request replace-path ^/httpbin(/.*)$ \1
    server httpbin httpbin.org:443 ssl verify none check

backend cat_fact
    server fact catfact.ninja:443 ssl verify none check

backend be_test
    http-request return status 200 content-type 'text/html' string 'Hello! You are authorized'

the httpbin_backend is working fine but the cat_fact backend is throwing error given below

[WARNING]  (8) : Server cat_fact/fact is DOWN, reason: Layer6 invalid response, info: "SSL handshake failure", check duration: 29ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
[ALERT]    (8) : backend 'cat_fact' has no server available!
Server cat_fact/fact is DOWN, reason: Layer6 invalid response, info: "SSL handshake failure", check duration: 29ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
backend cat_fact has no server available!

i am not sure why it is working for httpbin_backend and not for cat_fact backend. I am using these third party domains for test purpose.

My guess is that haproxy isnt sending the SNI data in the TLS handshake, and catfact requires it. I haven’t played with this myself but check out ‘sni’ and ‘check-sni’ keywords that you apply to the server

2 Likes

Yes you are correct it worked after introducing sni and check-sni in my server. It also required host header.
Thank you for your answer.