Wildcard SSL certificate SNI routing

I am a beginner with HAProxy.

I am using a mixed method with TCP and HTTP mode, so I can use a VPN server and web servers on TCP port 443.

Everything seems to be working for the most part, but I’m encountering an issue with SSL certificates, or at least, I think that’s the problem.

When I use a separate SSL certificate for each domain, everything functions as expected. However, when I switch to a wildcard SSL certificate (*.domain.com), I get unexpected behavior: it redirects me to the first backend I accessed.

When I first open sub1.domain.com, the correct website loads. However, if I open sub2.domain.com, sub3.domain.com, or sub4.domain.com in separate tabs within the same browser, the same backend as sub1.domain.com is displayed for all of them. If I wait for a while and then click the refresh button, the correct website is displayed.

If I close and reopen the browser, I can connect to the specific subdomain without any issues. However, when I try to visit a different subdomain, it redirects me to the same backend I initially accessed - unless I either don’t close the browser or wait for a while.

It seems like the server is not properly handling SNI checks.

Maybe someone knows if I need to configure anything else to make it work correctly with a wildcard SSL certificate? As I mentioned, no issues are detected if I use a separate SSL certificate for each subdomain.

here is my config:

frontend tls
bind *:443
mode tcp
option tcplog
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }
use_backend tcp_to_https if { req.ssl_sni -i sub1.domain.com }
use_backend tcp_to_https if { req.ssl_sni -i sub2.domain.com }
use_backend tcp_to_https if { req.ssl_sni -i sub3.domain.com }
use_backend tcp_to_https if { req.ssl_sni -i sub4.domain.com }
default_backend vpnserver

frontend https
bind 127.0.0.1:8443 accept-proxy ssl crt /etc/ssl/private/
http-request redirect scheme https unless { ssl_fc }
http-request set-header X-Forwarded-Proto https
http-response set-header Strict-Transport-Security “max-age=16000000; includeSubDomains; preload;”
option forwardfor
use_backend sub1 if { ssl_fc_sni -i sub1.domain.com }
use_backend sub2 if { ssl_fc_sni -i sub2.domain.com }
use_backend sub3 if { ssl_fc_sni -i sub3.domain.com }
use_backend sub4 if { ssl_fc_sni -i sub4.domain.com }

backend tcp_to_https
mode tcp
server https 127.0.0.1:8443 check send-proxy-v2

backend vpnserver
mode tcp
option tcp-check
timeout connect 30s
timeout server 30s
retries 3
server vpn 172.25.254.2:38443

backend sub1
mode http
server node01 192.168.10.9:38701 weight 1 maxconn 8192 check ssl verify none

backend sub2
mode http
server node01 192.168.10.243:5002

backend sub3
mode http
server node01 192.168.10.243:5003

backend sub4
mode http
server node01 192.168.10.243:5009

This is expected behavior with this configuration and has been documented and discussed many times.

The SNI value is send to haproxy once, during the TLS handshake. When a browser sees a wildcard certificate, it will use the same TLS connections for everything that is covered by the wildcard certificate.

For traffic decrypted by haproxy (your frontend https on port 8443), fortunately you can just switch from ssl_fc_sni to hdr(host) as per the ssl_fc_sni documentation:

CAUTION! Except under very specific conditions, it is normally not correct to use this field as a substitute for the HTTP “Host” header field. For example, when forwarding an HTTPS connection to a server, the SNI field must be set from the HTTP Host header field using “req.hdr(host)” and not from the front SNI value. The reason is that SNI is solely used to select the certificate the server side will present, and that clients are then allowed to send requests with different Host values as long as they match the names in the certificate. As such, “ssl_fc_sni” should normally not be used as an argument to the “sni” server keyword, unless the backend works in TCP mode.

However you still have an overlap between vpnserver and https, if vpnserver is a HTTPS server.

In this case the best and easiest way around this would be to use a dedicated certificate for the VPN server that doesn’t match the wildcard at all. You can do this with a single TLD, just add another label.

vpn.vpn.domain.com is not matched by a wildcard *.domain.com certificate.

tcp-request content accept if { req_ssl_hello_type 1 } or !{ req_ssl_hello_type 1 }

This is certainly wrong and will lead to problems.

I’m assuming you don’t want to wait 5 seconds for the vpnserver traffic. Unless you can match the vpnserver traffic by payload, you will just have to lower the inspect-delay to 1 second.

But the rule you are using does not make sense and will lead to further issues, because basically you are not waiting for buffer to have a full TLS client hello required for SNI based routing.

2 Likes