Configuration working with curl but not with a browser

I have a simple haproxy configuration that looks like the following:

global
    # configure logging
    log stdout format raw local0 debug

    # set default parameters to the modern configuration
    tune.ssl.default-dh-param 2048
    ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHAC
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
    ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CH
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets


defaults
    mode    http
    option  forwardfor
    option  http-server-close

    stats   enable
    stats   uri /hastats
    stats   realm HAproxy\ Stats
    stats   auth *****:*****

    timeout client 30s
    timeout connect 4s
    timeout server 30s

frontend ft
    mode http
    option httpslog
    option logasap
    log global
    bind    :443 ssl crt /etc/certs
    bind    :80
    redirect scheme https code 301 if !{ ssl_fc }
    # HSTS (15768000 seconds = 6 months)
    http-response set-header Strict-Transport-Security max-age=15768000


    acl host_op_domain_com hdr(host) -i beg op.domain.com
    use_backend k8s_cluster_prod if host_op_domain_com


backend k8s_cluster_prod
    mode http
    option forwardfor
    http-send-name-header Host op.domain.com
    server node1 node1.domain.com:443 check ssl verify none
    server node2 node2.domain.com:443 check ssl verify none
    server node3 node3.domain.com:443 check ssl verify none
    server node4 node4.domain.com:443 check ssl verify none
    server node5 node5.domain.com:443 check ssl verify none

For testing purposes, I am running haproxy locally with docker listening on port 8443.
There, I updated my /etc/hosts file with the following line:

127.0.0.1 op.domain.com

It (kind of) works when I try to access the website with curl: curl -H "Host:op.domain.com" --insecure https://127.0.0.1:8443/. The nginx backend service is answering (even though the host header gets stripped - but it is a different problem).

The haproxy log looks like:

172.20.0.1:37700 [19/Jan/2024:16:32:12.647] ft~ k8s_cluster_prod/node1.domain.com 0/0/172/44/+216 404 +299 - - ---- 1/1/1/0/0 0/0 "GET https://op.domain.com/ HTTP/2.0" 0/0000000000000000/0/0/0 -/TLSv1.3/TLS_AES_256_GCM_SHA384

However, it does not throw the same error when I try to open the url in a browser (here Firefox). It returns a 503 error: No server is available to handle this request.

The haproxy error log contains:

172.20.0.1:56956 [19/Jan/2024:16:38:11.575] ft~ ft/<NOSRV> -1/-1/-1/-1/+0 503 +217 - - SC-- 1/1/0/0/0 0/0 "GET https://op.domain.com:8443/ HTTP/2.0" 0/0000000000000000/0/0/0 op.domain.com/TLSv1.3/TLS_AES_256_GCM_SHA384

It seems that the acl does not work as intended… but I can’t figure out why

Does anyone have an idea?

A browser would add the port number to the Host header (in cases where a non standard port is used) making your ACL not match anymore.

I would suggest a different method to reproduce the issue with curl:

curl --insecure --resolve op.domain.com:8443:127.0.0.1 https://op.domain.com:8443/

So instead of manually constructing the Host header (which in this case hid the problem from you), you are telling curl to overwrite DNS as per the --resolve directive.

I think curl would then behave the same, but I suggest you check by the headers by running it in verbose mode (-vv).

1 Like

Thanks for the quick reply!

You are right! I can reproduce the same behaviour with your curl command:

curl -vv --insecure --resolve op.domain.com:8443:127.0.0.1 https://op.domain.com:8443/

leads to the following log:

72.20.0.1:57166 [19/Jan/2024:17:25:40.384] ft~ ft/<NOSRV> -1/-1/-1/-1/+0 503 +217 - - SC-- 1/1/0/0/0 0/0 "GET https://op.domain.com:8443/ HTTP/2.0" 0/0000000000000000/0/0/0 op.domain.com/TLSv1.3/TLS_AES_256_GCM_SHA384

I’m now left with the question about the host header being stripped from the request to the backend server.

You are overwriting the Host header unconditionally in the backend:

http-send-name-header Host op.domain.com

I know. I’ll need to debug it further. But this is off-topic.
Thanks for your help!