301 redirect over https doesn't include HSTS header (HAProxy 2.1)

Hi!

I’m a new user to haproxy and I’m currently configuring it to route to my docker containers. My end goal is to reach an A+ on Qualys SSL Labs and enable my site to be included on the HSTS preload list.

According to HSTS preload, you must redirect to HTTPS and include preload header on base domain too (in the redirect).

The other question I have is: What’s the best way to add www prefix on base domain? I’d like to do this for my primary domain only.

Here’s my entire config:

global
    daemon
    log stdout format raw daemon warning
    maxconn 50000
    pidfile /var/run/haproxy.pid
    tune.ssl.default-dh-param 2048

    # multithreading
    nbproc 1
    nbthread 8
    cpu-map auto:1/all 0

    # set minimum SSL requirements
    ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
    ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
    ssl-dh-param-file /certs/dhparam.pem

defaults
    log global
    mode http
    maxconn 50000
    balance roundrobin
    option dontlognull
    option redispatch
    retries 3

    # connection timeouts
    timeout check           5s  # additional check timeout
    timeout client          30s # clientside maximum inactivity time
    timeout client-fin      5s  # inactivity time for half-closed connections
    timeout connect         5s  # time for a connection attempt to a server to succeed
    timeout http-keep-alive 30s # maximum allowed time to wait for a new HTTP request to appear
    timeout http-request    5s  # maximum allowed time to wait for a complete HTTP request
    timeout queue           30s # maximum time to wait in the queue for a connection slot to be free
    timeout server          5s  # maximum inactivity time on the server side
    timeout server-fin      5s  # inactivity timeout on the server side for half-closed connections
    timeout tarpit          5s  # duration for which tarpitted connections will be maintained
    timeout tunnel          1h  # maximum inactivity time on the client and server side for tunnels

frontend http-in
    bind *:80
    bind *:443 ssl crt /certs/example.net.pem alpn h2,http/1.1

    # default headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    http-response set-header X-Frame-Options "SAMEORIGIN"
    http-response set-header X-XSS-Protection "1; mode=block"
    http-response set-header X-Content-Type-Options "nosniff"

    # redirect all HTTP traffic to HTTPS
    http-request redirect scheme https code 301 if !{ ssl_fc }

    # route subdomains to backends
    acl is_main  hdr(host) -i example.net
    acl is_www   hdr(host) -i www.example.net

    use_backend main  if is_main
    use_backend www   if is_www

backend main
    # redirect non-www to www
    acl has_www hdr_beg(host) -i www
    http-request redirect prefix https://www.%[hdr(host)] code 301 if !{ has_www }

backend www
    server main main:9200 check

Bump.

Still curious about this issue. I’ve updated my config once again, no luck. I edited my post with my current config.

Yeah this is unfortunately a limitation in currently released versions of haproxy, all those add-headers/set-headers rules don’t work for locally emitted responses like error pages or redirects.

This has been implemented (as the http-after-response rule) in the development version of haproxy (v2.2-dev2) and will be released as haproxy 2.2.

Also see:

https://www.mail-archive.com/haproxy@formilux.org/msg29294.html

If you need this right now with haproxy, you either need to use LUA for the redirect or make secondary frontend/backends for the redirect and use the primary frontend/backend for the HSTS headers adjustment.

1 Like

Thanks a lot for the resources! Once 2.2 is stable I’ll be updating my config to use that.

Do you happen to have any quick examples of the LUA/secondary backends I could implement in my current config? Thanks!

All the redirects need to happen in a second proxy layer (in this case, the frontend redirects listening on 127.0.0.1:8081 - so make sure that port is free or use a different one). This also requires another backend, which is needsredirect here that is needed to divert the traffic to the new frontend.

The original frontend http-in must not contain any redirect rules (just the HSTS settings) and must make sure that request that still need to be redirected go through backend needsredirect to frontend redirects.

It should look like this then (untested):

frontend http-in
    bind *:80
    bind *:443 ssl crt /certs/example.net.pem alpn h2,http/1.1

    # default headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    http-response set-header X-Frame-Options "SAMEORIGIN"
    http-response set-header X-XSS-Protection "1; mode=block"
    http-response set-header X-Content-Type-Options "nosniff"

    # route subdomains to backends
    acl is_main  hdr(host) -i example.net
    acl is_www   hdr(host) -i www.example.net

    use_backend needsredirect  if !{ ssl_fc }
    use_backend needsredirect  if is_main
    use_backend www   if is_www

backend needsredirect
    server localredirects 127.0.0.1:8081

frontend redirects
    bind 127.0.0.1:8081
    
    # redirect non-www to www
    acl has_www hdr_beg(host) -i www.
    http-request redirect prefix https://www.%[hdr(host)] code 301 if !{ has_www }

    # redirect all HTTP traffic to HTTPS
    http-request redirect scheme https code 301 if !{ ssl_fc }

backend www
    server main main:9200 check
2 Likes

perfect, thank you! i’ll give it a go.

For anyone else who needs this behavior before HAProxy 2.2, this config works for me and won’t include HSTS headers on the HTTP -> HTTPS redirect too!

Doing all of this will make your domain ready for HSTS preloading if you submit it to the preload list. However, I recommend upgrading to HAProxy 2.2 once it’s stable and released so you don’t have to do this and instead just use the http-after-response rule.

Thanks again @lukastribus for the help!

frontend http-in
    bind :80
    bind :443 ssl crt /certs/example.net.pem alpn h2,http/1.1

    # default headers
    http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    http-response set-header X-Frame-Options "SAMEORIGIN"
    http-response set-header X-XSS-Protection "1; mode=block"
    http-response set-header X-Content-Type-Options "nosniff"

    # redirect HTTP to HTTPS
    http-request redirect scheme https code 301 if !{ ssl_fc }

    # route subdomains to backends
    use_backend www-redirect if { hdr(host) -i www.example.net     }
    use_backend main         if { hdr(host) -i example.net         }

backend www-redirect
    server local-www-redirect :8081

frontend redirect
    bind :8081

    # redirect www to non-www
    http-request redirect prefix https://example.net code 301 if { hdr(host) -i www.example.net }

backend main
    server main web-main:9200 check

Results:



1 Like