HAProxy unable to redirect to HTTPS when terminating

This is a duplicate of my SO question. You can answer there too, to get the reputation. Hope this doesn’t violate some rule.

I wanted to setup HAProxy for two servers - one with passthroug one with termination. I was able to do it with no previous experience of HAProxy, but I am unable to make HTTPS redirect for the terminating one - I get 502. Here is the config:

#Upgrades the passthrough and check for Let's Encrypt
frontend http_front
    bind :80
    option forwardfor
    acl host_s1 hdr(host) -i s1.example.com
    acl path_le path_beg -i /.well-known/acme-challenge/
    redirect scheme https code 301 if host_s1 !path_le
    use_backend acmetool if path_le
    default_backend http-back

#Handles the passthrough and loopsback to itself for other domains
frontend passthrough
    mode tcp
    bind :443
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    use_backend service1 if { req_ssl_sni -i s1.example.com }
    default_backend https-back

#Loopback to handle the termination domains
frontend https-front
    bind 127.0.0.1:8443 ssl crt s2.example.com.pem
    option forwardfor
    reqdel X-Forwarded-Proto
    reqadd X-Forwarded-Proto:\ https if { ssl_fc }
    use_backend service2 if { req_ssl_sni -i s2.example.com }
    default_backend service2

#returns for second pass from HTTP
backend http-back
    server https-front 127.0.0.1:8443

#returns for second pass from HTTPS
backend https-back
    mode tcp
    server https-front 127.0.0.1:8443

backend service1
    mode tcp
    server service1 127.0.0.1:8888

backend service2
    #redirect scheme https code 301 if !{ ssl_fc }
    server server2 server2:80

backend acmetool
    server acmetool 127.0.0.1:81

Not sure if I need those reqdel/reqadd in https-front. Or if I have to do tcp-request again on the second pass for HTTPS.

Uncommenting the redirect on the backend does not help either.

I also had send-proxy-v2 to the initial backends with no change in the result. I am not experienced enough in either Linux or HAproxy to know hoe to use sockets, so I replaced them with ports for the loop-back. Would love to know if there is any difference in efficiency between them.

Share the entire configuration please; we need to know what default and global settings are set.
Also enable logging and provide the output when the requests fails.

A few comments:

  • the backend http-back makes no sense, what are you trying to achieve there? You are sending cleartext traffic (HTTP) to a HTTPS port, that’s not going to work
  • in frontend https-front the fetch must be ssl_fc_sni instead of req_ssl_sni, because your terminating SSL here. But the entire use_backend statement is unnecessary anyway, default_backend is enough unless you have other needs
  • can you confirm server2 responds to HTTP request on port 80?

I used the default settings of the installation:
global
maxconn 64
chroot /var/lib/haproxy
stats socket /run/haproxy.sock mode 660 level admin
stats timeout 30s
user root
group root
daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private
	ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:RC4-SHA:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL

defaults
	log     global
	mode    http
	timeout connect 5s
	timeout client  30s
	timeout server  30s
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

I am not sure how to enable the logging as I am using a customized Debian distro (DietPi.com) that don’t seem to have rsyslog enabled by default. Is there a way to monitor the output without logging service?

As for the comments:

  • backend http-back is required since I am using different modes in frontend http_front and backend https-back so I cannot use just one.
    I thought looping back like this would be possible workaround. I also thought that the HTTP will get upgraded when sent to the frontend that terminates the connection. This is the main problem I have redirecting both types of connections - terminating and passthrough.
  • Thanks, updated it. It’s not needed now, but I would possibly have other services in the future, so I’m preparing it. This way I won’t have to search again for the required setup :slight_smile:
  • it does work when accessed from the local network and also when accessed with a portforward from the internet. It also works when accessing directly with https://s2.example.com

You log it to some destination IP and trace the syslog message with tcpdump or wireshark. Use the capture filter udp port 514.

My problem is that I don’t know what you would like to do here. Can you explain? Would you like to encapsulate this to HTTPS and keep using HTTP on the frontend?

Then modify your backend like this:

#returns for second pass from HTTP
backend http-back
    server https-front 127.0.0.1:8443 ssl verify none

Sorry for not being able to explain my problem properly. I’ll try again:

I have two services s1.example.com and s2.example.com pointed to this HAProxy.
S1 is handling TLS itself so I use passthrough for it (frontend passthrough) and I also have:

acl host_s1 hdr(host) -i s1.example.com
acl path_le path_beg -i /.well-known/acme-challenge/
redirect scheme https code 301 if host_s1 !path_le

Which redirects any HTTP to HTTPS for this server. This works fine and without errors.

S2 cannot handle TLS itself, so i have to terminate on the HAProxy with frontend https-front. I created additional backend https-back and frontend https-front to handle the loopback for the second pass. Due to the difference in the modes, I had to create additional backend http-back or I get error in the configuration.
This though is insufficient since I get 502 when accessing HTTP. But HTTPS works fine.

Your suggestion partially fixed my issue - now when I access s2.example.com on HTTP, I am do not get the 502 error message. But it does not get redirected to HTTPS either - I just get HTTP traffic.

So my question is where could I do another redirect/upgrade of the connection so it will automatically go to HTTPS when accessing S2 over HTTP?

P.S. after writing this I tested with:

backend http-back
       redirect scheme https code 301 if !{ ssl_fc }
       server https-front 127.0.0.1:8443 ssl verify none

and this seems to do the redirect properly.

I would appreciate any further comments if something is not done properly. And if there is any advantage if using sockets instead of ports for the loopback.

This is really more simple than that.

You have already configured frontend http_front to redirect to HTTPS ONLY if the host is s1.example.com (and its not Let’s Encrypt):

acl host_s1 hdr(host) -i s1.example.com
acl path_le path_beg -i /.well-known/acme-challenge/
redirect scheme https code 301 if host_s1 !path_le

If you want to redirect everything, just remove the ACL condition here, or add the s2 to the ACLs.

You can remove backend http-back completely, it does not have any purpose at this point.

I understand. I did it like this because I merged two separate configs found online and did not understood the loopback idea and the redirect clause quite well. Now, after your help, it’s more clear :slight_smile:

Here is the final setup for anyone interested with updated names to better reflect the logic behind them:

#Upgrades to HTTPS unless it's Let's Encrypt
frontend http
    bind :80
    option forwardfor
    redirect scheme https code 301 if !{ path_beg -i /.well-known/acme-challenge/ }
    default_backend acmetool

#Handles the passthrough and loopsback for termination
frontend passthrough
    mode tcp
    bind :443
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }
    use_backend service1 if { req_ssl_sni -i s1.example.com }
    default_backend loopback

#Handles the termination domains on second pass
frontend termination
    bind 127.0.0.1:8443 ssl crt s2.example.com.pem
    option forwardfor
    reqdel X-Forwarded-Proto
    reqadd X-Forwarded-Proto:\ https if { ssl_fc }
    use_backend service2 if { ssl_fc_sni -i s2.example.com }
    default_backend service2

#Loopback for second pass
backend loopback
    mode tcp
    server https-front 127.0.0.1:8443

backend service1
    mode tcp
    server service1 127.0.0.1:8888

backend service2
    server server2 server2:80

backend acmetool
    server acmetool 127.0.0.1:81

Thanks again for the help with understanding the logic :+1: