Updated haproxy ssl pass-through config file for backend domains

Hello there

This is my first post and I really wanted to instead to post a question of a problem, I wanted to post a solution to a problem by sharing my haproxy.cfg file so I didn't know where exactly where to post it (just wanted to give back to the community). I've seen this topic popup a lot out there and after trying different methods, I finally got a very nice config file to solve the issue of not being able to redirect ssl traffic to several backend webservers without having to copy the ssl certificates and keys into openwrt and let the backend server handle them individually. These webservers are running in two different physical machines connected directly to a nanopi r4s. I'm open to ear some feedback!

Check your configuration with: haproxy -c -f /etc/haproxy.cfg
If all goes well restart haproxy: /etc/init.d/haproxy restart

# Global settings
global
    daemon            # Run HAProxy in daemon mode
    nosplice          # Disable using splice for receiving and transmitting data

# Default settings
defaults
    log global        # Log messages to the global log facility
    mode http         # Set the default mode to HTTP
    option httplog    # Enable HTTP request/response logging
    log 127.0.0.1:514 local0  # Log messages to the local syslog server
    log /var/log/haproxy.log local0  # Log messages to a custom log file
    timeout client 30s  # Set client timeout to 30 seconds
    timeout connect 30s  # Set connect timeout to 30 seconds
    timeout server 30s   # Set server timeout to 30 seconds

# Frontend for HAProxy statistics
frontend stats
    bind *:9000       # Listen on port 9000
    mode http         # Set mode to HTTP
    stats enable      # Enable statistics reporting
    stats uri /haproxy  # Set URI path for accessing statistics
    stats realm HAProxy\ Statistics  # Set realm for HTTP authentication
    stats auth username:password  # Set username and password for HTTP authentication

# Frontend for HTTP traffic
frontend http_in
    mode http         # Set mode to HTTP
    option httplog    # Enable HTTP request/response logging
    bind *:80         # Listen for HTTP traffic on port 80
    http-request redirect scheme https  # Redirect HTTP traffic to HTTPS
    option forwardfor  # Add X-Forwarded-For header to forwarded requests
    # Add enhanced security headers
    http-response add-header Strict-Transport-Security max-age=31536000;\ includeSubDomains;\ preload  # Add HSTS header
    http-response add-header Content-Security-Policy default-src\ 'self'  # Add CSP header

# Frontend for HTTPS traffic
frontend https_in
    mode tcp          # Set mode to TCP for handling encrypted traffic
    option tcplog     # Enable TCP logging
    bind *:443        # Listen for HTTPS traffic on port 443
    acl tls req.ssl_hello_type 1  # Check for TLS handshake
    tcp-request inspect-delay 5s  # Delay inspection of traffic by 5 seconds
    tcp-request content accept if { req_ssl_hello_type 1 }  # Accept traffic after TLS handshake
    # Track session data for rate limiting
    stick-table type ip size 100k expire 30m  # Define session tracking table
    tcp-request content track-sc0 src  # Track session data based on source IP
    # Use backend based on SNI
    use_backend %[req_ssl_sni,lower,word(1,:)]_tls  # Select backend based on SNI

# Backend servers for HTTPS traffic
backend example1.com_tls
    mode tcp          # Set mode to TCP
    server example1.com 192.168.1.101:443 check  # Define backend server and its IP address

backend example2.com_tls
    mode tcp          # Set mode to TCP
    server example2.com 192.168.1.102:443 check  # Define backend server and its IP address

backend example3.com_tls
    mode tcp          # Set mode to TCP
    server example3.com 192.168.1.103:443 check  # Define backend server and its IP address

backend example4.com_tls
    mode tcp          # Set mode to TCP
    server example4.com 192.168.1.104:443 check  # Define backend server and its IP address
2 Likes

Thanks for this timely post as I'm setting up HAProxy for a reverse proxy.

I noticed you use SNI for identification instead of the recommended ACL in the docs... what was the reasoning for this?

Edit: I tried and it seems SNI is the only way to id which backend to use for https.

I had configured my http reverse proxy servers but was stumped on my https servers (I'm not redirecting, I have both http and https) Your post helped a lot - thank you!

The acl tls req.ssl_hello_type 1 creates an ACL (Access Control List) named tls that checks for the presence of a TLS handshake (req.ssl_hello_type 1 ) and the use_backend %[req_ssl_sni,lower,word(1,:)]_tls : Selects the backend based on the server name Indication (SNI) provided by the client during the TLS handshake. This allows HAProxy to route traffic to the appropriate backend server based on the hostname requested by the client. So in short, when a request comes to reach example.com haproxy first makes sure that is a valid https request then "reads" the domain name (example.com) and then redirects to the right local ip address. I though this was a simpler approach than configuring haproxy with specific ACLs and managing all the certificates for my servers. Also I believe this approach reduces the load in openwrt because it is not processing much info.. just passing along the packages to another device to handle them and ensuring that no decryption is done until the package gets to the final destination. Cheers!

Good approach, thank you!

You should "sandwich" your config between two rows of backtick characters ` (which themselves will be invisible in the preview) looking in something like this in the editor:
```
Your Pasted Text as preformatted text with fixed width font
1
1111 (note with fixed-width fonts the numbers are right-aligned)
```
but looking like this in the rendered forum:

Your Pasted Text as preformatted text with fixed width font
   1
1111 (note with fixed-width fonts the numbers are right-aligned)
1 Like

I can't find an option to edit my initial post so here it goes preformatted.

# Global settings
global
daemon # Run HAProxy in daemon mode
nosplice # Disable using splice for receiving and transmitting data

# Default settings
defaults
log global # Log messages to the global log facility
mode http # Set the default mode to HTTP
option httplog # Enable HTTP request/response logging
log 127.0.0.1:514 local0 # Log messages to the local syslog server
log /var/log/haproxy.log local0 # Log messages to a custom log file
timeout client 30s # Set client timeout to 30 seconds
timeout connect 30s # Set connect timeout to 30 seconds
timeout server 30s # Set server timeout to 30 seconds

# Frontend for HAProxy statistics
frontend stats
bind *:9000 # Listen on port 9000
mode http # Set mode to HTTP
stats enable # Enable statistics reporting
stats uri /haproxy # Set URI path for accessing statistics
stats realm HAProxy\ Statistics # Set realm for HTTP authentication
stats auth username:password # Set username and password for HTTP authentication

# Frontend for HTTP traffic
frontend http_in
mode http # Set mode to HTTP
option httplog # Enable HTTP request/response logging
bind *:80 # Listen for HTTP traffic on port 80
http-request redirect scheme https # Redirect HTTP traffic to HTTPS
option forwardfor # Add X-Forwarded-For header to forwarded requests
# Add enhanced security headers
http-response add-header Strict-Transport-Security max-age=31536000;\ includeSubDomains;\ preload # Add HSTS header
http-response add-header Content-Security-Policy default-src\ 'self' # Add CSP header

# Frontend for HTTPS traffic
frontend https_in
mode tcp # Set mode to TCP for handling encrypted traffic
option tcplog # Enable TCP logging
bind *:443 # Listen for HTTPS traffic on port 443
acl tls req.ssl_hello_type 1 # Check for TLS handshake
tcp-request inspect-delay 5s # Delay inspection of traffic by 5 seconds
tcp-request content accept if { req_ssl_hello_type 1 } # Accept traffic after TLS handshake # Track session data for rate limiting
stick-table type ip size 100k expire 30m # Define session tracking table
tcp-request content track-sc0 src # Track session data based on source IP # Use backend based on SNI
use_backend %[req_ssl_sni,lower,word(1,:)]_tls # Select backend based on SNI

# Backend servers for HTTPS traffic
backend example1.com_tls
mode tcp # Set mode to TCP
server example1.com 192.168.1.101:443 check # Define backend server and its IP address

backend example2.com_tls
mode tcp # Set mode to TCP
server example2.com 192.168.1.102:443 check # Define backend server and its IP address

backend example3.com_tls
mode tcp # Set mode to TCP
server example3.com 192.168.1.103:443 check # Define backend server and its IP address

backend example4.com_tls
mode tcp # Set mode to TCP
server example4.com 192.168.1.104:443 check # Define backend server and its IP address
1 Like

Really appreciate the information you posted on setting up SSL pass through with haproxy. I follow everything except I'm trying to figure out exactly what this line does.

use_backend %[req_ssl_sni,lower,word(1,:)]_tls # Select backend based on SNI

I've been trying to figure out what scripting language haproxy is using so I can look up what word(1,") does but haven't had much luck in tracking that down.

I'm hoping to do splitting of traffic between different back end http servers based on subdomain, not domain, so I need to figure that out still and I think that word(1,") has something to do with it.

It would also help if I could get logging of haproxy to report to the openwrt logd service, but I haven't been able to make that work either. For now I stood up a temp syslog compatible service that I managed to get logs flowing to.

It just makes the front end match to a back end, but they have to be an exact match:

subdomain.yourdomain.com
subdomain2.yourdomain.com

would match to:

Backend:
subdomain.yourdomain.com
subdomain2.yourdomain.com

UPDATE: I started having problems with the acme challenge for a vm web server in proxmox so it was time to review the previous configuration. What this config does is to make sure acme challenges don't get redirected to https and also I defined 2 acls that gather the domains belonging to 2 web servers so http traffic goes to the server it belongs to. Additionally, there are a few more security improvements. It is working so far without crashing.

global
        daemon
        nosplice

defaults
        log global
        mode http
        option httplog
        log 127.0.0.1:514 local0
        log /var/log/haproxy.log local0
        timeout client 30s
        timeout connect 30s
        timeout server 30s

frontend stats
        bind *:9000  # You can choose any port you prefer
        mode http
        stats enable
        stats uri /haproxy  # You can customize the URI path
        stats realm HAProxy\ Statistics
        stats auth username:password  # Choose a secure username and password

frontend http_in
        mode http
        option httplog
        bind *:80

        # Rate limiting
        stick-table type ip size 1m expire 10m store gpc0
        http-request track-sc0 src
        http-request deny if { src_conn_cur gt 100 }  # Limit to 100 requests per IP

        # Allow ACME challenge requests to bypass redirect
        acl acme_challenge path_beg /.well-known/acme-challenge/
        acl webserver_A_hosts hdr(host) -i site.one site.two
        acl webserver_B_hosts hdr(host) -i site.three site.four

        http-request redirect scheme https unless acme_challenge
        use_backend acme_backend_A if acme_challenge webservers_A_hosts
        use_backend acme_backend_B if acme_challenge webservers_B_hosts

        option forwardfor
        # Enhanced security headers
        http-response add-header Strict-Transport-Security max-age=31536000;\ includeSubDomains;\ preload
        http-response add-header Content-Security-Policy default-src\ 'self'
        http-response add-header X-Content-Type-Options nosniff
        http-response add-header X-Frame-Options DENY
        http-response add-header X-XSS-Protection "1; mode=block"

frontend https_in
        mode tcp
        option tcplog
        bind *:443
        acl tls req.ssl_hello_type 1
        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }

        # Track session data for rate limiting
        stick-table type ip size 100k expire 30m
        tcp-request content track-sc0 src
        # Use backend based on SNI
        use_backend %[req_ssl_sni,lower,word(1,:)]_tls

# Backend for ACME challenges
backend acme_backend_A
        mode http
        option httpchk
        default-server inter 3s fall 3 rise 2
        server webserver_A 192.168.1.10:80 check

backend acme_backend_B
        mode http
        option httpchk
        default-server inter 3s fall 3 rise 2
        server webserver_B 192.168.1.11:80 check

# Normal HTTPS traffic to backends

backend site.one_tls
        mode tcp
        option ssl-hello-chk
        server site.one 192.168.1.154:443 check

backend site.two_tls
        mode tcp
        option ssl-hello-chk
        server site.two 192.168.1.55:443 check

backend site.three_tls
        mode tcp
        option ssl-hello-chk
        server site.three 192.168.1.77:443 check