Redirect HTTP to HTTPS with custom Ports

Hello,

Before I begin let me just say that I am unable to provide the content of the config file, as most of it is not human readable because I am using the HAProxy plugin for OPNsense.

I have a server that is hosting multiple websites on different ports.

Schematic

ser.ver.com      (website_A) --> HTTP  --> :80   OPNsense --> noSSL --> :80   Server
ser.ver.com      (website_A) --> HTTPS --> :443  OPNsense -->   SSL --> :443  Server
ser.ver.com:8040 (website_B) --> HTTPS --> :8040 OPNsense --> noSSL --> :8040 Server
ser.ver.com:8080 (website_C) --> HTTPS --> :8080 OPNsense --> noSSL --> :8080 Server
ser.ver.com:8888 (website_D) --> HTTPS --> :8888 OPNsense -->   SSL --> :8888 Server

Each port has a single frontend. The only difference is the port they are listening on and the rules attached to them.

frontend WAN2_HTTPS_frontend
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    bind 192.168.223.252:443 name 192.168.223.252:443 curves secp384r1 ssl no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:!CAMELLIA:!AESCCM:!AES128:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS crt-list /tmp/haproxy/ssl/6037a492f395c5.28503768.certlist
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

Each port has a single frontend.
I already configured everything so I can access all the websites, but this only works if I put “https://” in front of the URL, f.e. by typing “https://ser.ver.com:8080” in my browser.

Until now I have been using an “HTTPtoHTTPS_Redirect_rule” on the HTTP_frontend, which works as shown below:
If
“Traffic is SSL (TCP request content inspection)_negated_condition” and “no_acme_challenge_condition”
== true
then
http-request redirect code 301 scheme https

I have been using this rule without any issues everytime I wanted to force the use of HTTPS on one of my services.

Now let’s get to my problem.
This rule works perfectly fine for hosts that have no “:Port” the end.
But it seems like if the access URL (f.e. http://ser.ver.com:8080) has a custom port at the end, the rule is not working the way it should.

Most browser don’t provide a useful error message, luckily Google Chrome does!

Accessing http://ser.ver.com:8080/ without redirect_rule in 8080_frontend --> ERR_EMPTY_RESPONSE
Accessing http://ser.ver.com:8080/ with redirect_rule in 8080_frontend --> ERR_EMPTY_RESPONSE
Accessing https://ser.ver.com:8080/ without redirect_rule in 8080_frontend --> WORKS
Accessing https://ser.ver.com:8080/ with redirect_rule in 8080_frontend --> ERR_TOO_MANY_REDIRECTS

I need the rule to just replace “http” with “https” so that the traffic is SSL encrypted but the port at the end is not removed / changed.

Thanks in advance.

You need a custom redirect, like:

http-request redirect prefix https://ser.ver.com:8080 code 301

I already tried that but this also gives me.

ERR_TOO_MANY_REDIRECTS

Look at the exact HTTP responses causing the redirect (F12 developer tools, curl -v, etc) to find out how this redirect loop occurs.

Please find below the result of curl -v.

curl -v
[archie@archlinux ~]$ echo https_redirect_rule not set
https_redirect_rule not set


[archie@archlinux ~]$ curl -v http://ser.ver.com:8080
*   Trying 1.2.3.4:8080...
* Connected to ser.ver.com (1.2.3.4) port 8080 (#0)
> GET / HTTP/1.1
> Host: ser.ver.com:8080
> User-Agent: curl/7.75.0
> Accept: */*
> 
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server


[archie@archlinux ~]$ curl -v https://ser.ver.com:8080
*   Trying 1.2.3.4:8080...
* Connected to ser.ver.com (1.2.3.4) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=ser.ver.com
*  start date: Mar  1 07:20:20 2021 GMT
*  expire date: May 30 07:20:20 2021 GMT
*  subjectAltName: host "ser.ver.com" matched cert's "ser.ver.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: ser.ver.com:8080
> User-Agent: curl/7.75.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< cache-control: no-cache
< content-type: text/html; charset=utf-8
< expires: -1
< location: /login
< pragma: no-cache
< set-cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
< x-frame-options: deny
< date: Mon, 08 Mar 2021 11:34:53 GMT
< content-length: 29
< strict-transport-security: max-age=63072000; includeSubDomains; preload
< 
<a href="/login">Found</a>.

* Connection #0 to host ser.ver.com left intact






[archie@archlinux ~]$ echo https_redirect_rule set
https_redirect_rule set


[archie@archlinux ~]$ curl -v http://ser.ver.com:8080
*   Trying 1.2.3.4:8080...
* Connected to ser.ver.com (1.2.3.4) port 8080 (#0)
> GET / HTTP/1.1
> Host: ser.ver.com:8080
> User-Agent: curl/7.75.0
> Accept: */*
> 
* Empty reply from server
* Closing connection 0
curl: (52) Empty reply from server


[archie@archlinux ~]$ curl -v https://ser.ver.com:8080
*   Trying 1.2.3.4:8080...
* Connected to ser.ver.com (1.2.3.4) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: none
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=ser.ver.com
*  start date: Mar  1 07:20:20 2021 GMT
*  expire date: May 30 07:20:20 2021 GMT
*  subjectAltName: host "ser.ver.com" matched cert's "ser.ver.com"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: ser.ver.com:8080
> User-Agent: curl/7.75.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< content-length: 0
< location: https://ser.ver.com:8080/
< 
* Connection #0 to host ser.ver.com left intact
[archie@archlinux ~]$ 

Share the entire configuration.

1 Like
#
# Automatically generated configuration.
# Do not edit this file manually.
#

global
    # NOTE: Could be a security issue, but required for some feature.
    uid                         80
    gid                         80
    chroot                      /var/haproxy
    daemon
    stats                       socket /var/run/haproxy.socket group proxy mode 775 level admin
    nbproc                      1
    nbthread                    1
    maxconn                     10000
    tune.ssl.default-dh-param   4096
    spread-checks               0
    tune.chksize                16384
    tune.bufsize                16384
    tune.lua.maxmem             0
    log /var/run/log local0 info
    ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
    ssl-default-bind-ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:!CAMELLIA:!AESCCM:!AES128:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS

defaults
    log     global
    option redispatch -1
    timeout client 30s
    timeout connect 30s
    timeout server 30s
    retries 3

# autogenerated entries for ACLs

# autogenerated entries for config in backends/frontends

# autogenerated entries for stats


# Frontend: WAN2_HTTP_frontend (Listening on 192.168.0.252:80 (my.public.ip:80))
frontend WAN2_HTTP_frontend
    bind 192.168.0.252:80 name 192.168.0.252:80 
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

    # logging options
    # ACL: find_acme_challenge
    acl acl_5efdd933f3d8e7.91218356 path_beg -i /.well-known/acme-challenge/
    # ACL: CONWISE_condition
    acl acl_5f981b20144b30.33310615 hdr(host) -i conwise.domain.tld

    # ACTION: redirect_acme_challenges
    use_backend acme_challenge_backend if acl_5efdd933f3d8e7.91218356
    # ACTION: CONWISE_80_rule
    use_backend CONWISE_80_backend if acl_5f981b20144b30.33310615

# Frontend: WAN2_HTTPS_frontend (Listening on 192.168.0.252:443 (my.public.ip:443))
frontend WAN2_HTTPS_frontend
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    bind 192.168.0.252:443 name 192.168.0.252:443 curves secp384r1 ssl no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:!CAMELLIA:!AESCCM:!AES128:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS crt-list /tmp/haproxy/ssl/6037a492f395c5.28503768.certlist 
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

    # logging options
    # ACL: CONWISE_condition
    acl acl_5f981b20144b30.33310615 hdr(host) -i conwise.domain.tld

    # ACTION: CONWISE_443_rule
    use_backend CONWISE_443_backend if acl_5f981b20144b30.33310615


# Frontend: WAN2_8040_frontend (Listening on 192.168.0.252:8040 (my.public.ip:8040))
frontend WAN2_8040_frontend
    http-response set-header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    bind 192.168.0.252:8040 name 192.168.0.252:8040 curves secp384r1 ssl no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA!RC4:!CAMELLIA:!AESCCM:!AES128:!RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS crt-list /tmp/haproxy/ssl/6046397cc388d3.50059141.certlist 
    mode http
    option http-keep-alive
    option forwardfor
    # tuning options
    timeout client 30s

    # logging options
    # ACL: NoSSL_condition
    acl acl_5f030d86c69685.61231932 req.ssl_ver gt 0
    # ACL: no_acme_challenge
    acl acl_5f02e0c380fba9.85929270 path_beg -i /.well-known/acme-challenge/
    # ACL: CONWISE_8040_condition
    acl acl_6037c0a1bb38c9.71766845 hdr(host) -i conwise.domain.tld:8040

    # ACTION: HTTPtoHTTPS_rule
    http-request redirect code 301 scheme https if !acl_5f030d86c69685.61231932 !acl_5f02e0c380fba9.85929270

    # ACTION: CONWISE_8040_rule
    use_backend CONWISE_8040_backend if acl_6037c0a1bb38c9.71766845

# Backend: acme_challenge_backend (Added by Let's Encrypt plugin)
backend acme_challenge_backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server acme_challenge_host 127.0.0.1:43580 

# Backend: CONWISE_443_backend ()
backend CONWISE_443_backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server CONWISE_443_server 192.168.20.46:443 ssl verify none

# Backend: CONWISE_8040_backend ()
backend CONWISE_8040_backend
    # health checking is DISABLED
    mode http
    balance source
    # stickiness
    stick-table type ip size 50k expire 30m  
    stick on src
    # tuning options
    timeout connect 30s
    timeout server 30s
    http-reuse safe
    server CONWISE_8040_server 192.168.20.46:8040 

As soon as I add my HTTPtoHTTPS_rule to the 8040_frontend I get the redirect error.
Adding it to my HTTP_frontend works just fine, but I don’t need/want it there.

    # logging options
    # ACL: NoSSL_condition
    acl acl_5f030d86c69685.61231932 req.ssl_ver gt 0
    # ACL: no_acme_challenge
    acl acl_5f02e0c380fba9.85929270 path_beg -i /.well-known/acme-challenge/

    # ACTION: HTTPtoHTTPS_rule
    http-request redirect code 301 scheme https if !acl_5f030d86c69685.61231932 !acl_5f02e0c380fba9.85929270

A direct redirect like you already mentioned gives the same error.

http-request redirect prefix https://conwise.domain.tld:8040 code 301

Above you called curl -v https://ser.ver.com:8080 twice, and the result was different (one redirect to /login) and another simply redirecting to itself (which will clearly cause a loop). Why two different outputs, do you know?

    acl acl_5f030d86c69685.61231932 req.ssl_ver gt 0

Please replace req.ssl_ver gt 0 with ssl_fc.

The first curl was without the https_redirect_rule set on the frontend.
The second curl was with the https_redirect_rule set on the frontend.

Sadly this isn’t possible on HAProxy for OPNsense (as far as I know) as configs made in the haproxy.conf are ignored and overwritten once the service restarts.

Even though this won’t solve the original problem I got my services working now.
I managed to change their configs so they all now run on port 443 with a custom subpath for each of them.
The https-redirect is now also working as expected.

Thank you @lukastribus for your help.