Skip to content

Instantly share code, notes, and snippets.

@mbentley
Last active September 17, 2025 12:07
Show Gist options
  • Save mbentley/0e887c2af7863a562146ee23b121fb33 to your computer and use it in GitHub Desktop.
Save mbentley/0e887c2af7863a562146ee23b121fb33 to your computer and use it in GitHub Desktop.
HAProxy SNI Example
global
log /dev/log local0
log /dev/log local1 notice
defaults
log global
mode tcp
option tcplog
option dontlognull
timeout connect 5s
timeout client 50s
timeout client-fin 50s
timeout server 50s
timeout tunnel 1h
default-server inter 15s fastinter 2s downinter 5s rise 3 fall 3
### standalone stats page
listen stats
# accessible at http://192.168.1.100/haproxy?stats
bind 0.0.0.0:8181
mode http
option httplog
stats enable
stats admin if TRUE
stats refresh 5m
### frontend servers
frontend http
bind 0.0.0.0:80
mode http
# redirects from http to https
redirect scheme https code 301 if { hdr(Host) -i site1.example.com } !{ ssl_fc }
redirect scheme https code 301 if { hdr(Host) -i site1alias.example.com } !{ ssl_fc }
redirect scheme https code 301 if { hdr(Host) -i site2.example.com } !{ ssl_fc }
redirect scheme https code 301 if { hdr(Host) -i site3.example.com } !{ ssl_fc }
redirect scheme https code 301 if { hdr(Host) -i site4.example.com } !{ ssl_fc }
# set default backend
default_backend default_http
frontend https
option tcplog
bind 0.0.0.0:443
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
## exact matches
use_backend site1_https if { req.ssl_sni -i site1.example.com or site1alias.example.com }
use_backend site2_https if { req.ssl_sni -i site2.example.com }
use_backend site3_https if { req.ssl_sni -i site3.example.com }
use_backend site4_https if { req.ssl_sni -i site4.example.com }
# set default backend
default_backend default_https
### backend servers
backend default_http
mode http
server nginx_lb_http.sock unix@/run/nginx_lb_http.sock send-proxy weight 100 check
backend default_https
mode tcp
server nginx_lb_https.sock unix@/run/nginx_lb_https.sock send-proxy weight 100 check
backend site1_https
mode tcp
option ssl-hello-chk
server server1:443 192.168.1.101:443 weight 100 check
backend site2_https
mode tcp
option ssl-hello-chk
server server2:443 192.168.1.102:443 weight 100 check
backend site3_https
mode tcp
option ssl-hello-chk
server server3:443 192.168.1.103:443 weight 100 check
backend site4_https
mode tcp
option ssl-hello-chk
server server4:443 192.168.1.104:443 weight 100 check
@pipethedev
Copy link

What about ssl certificates ? How do i handle them, i am having problems with those

@mbentley
Copy link
Author

What about ssl certificates ? How do i handle them, i am having problems with those

No idea if this actually works because I do not terminate SSL at the haproxy layer for anything but here's an example I generated:

global
        log /dev/log local0
        log /dev/log local1 notice
        # SSL/TLS configuration for termination
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-options ssl-min-ver TLSv1.2 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
        ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
        # Tune SSL performance
        tune.ssl.default-dh-param 2048

defaults
        log global
        mode tcp
        option tcplog
        option dontlognull
        timeout connect 5s
        timeout client 50s
        timeout client-fin 50s
        timeout server 50s
        timeout tunnel 1h
        default-server inter 15s fastinter 2s downinter 5s rise 3 fall 3

### standalone stats page
listen stats
        # accessible at http://192.168.1.100:8181/haproxy?stats
        bind 0.0.0.0:8181
        mode http
        option httplog
        stats enable
        stats admin if TRUE
        stats refresh 5m

### frontend servers - HTTP (redirects only)
frontend http
        bind 0.0.0.0:80
        mode http
        option httplog
        # redirects from http to https
        redirect scheme https code 301 if { hdr(Host) -i site1.example.com } !{ ssl_fc }
        redirect scheme https code 301 if { hdr(Host) -i site1alias.example.com } !{ ssl_fc }
        redirect scheme https code 301 if { hdr(Host) -i site2.example.com } !{ ssl_fc }
        redirect scheme https code 301 if { hdr(Host) -i site3.example.com } !{ ssl_fc }
        redirect scheme https code 301 if { hdr(Host) -i site4.example.com } !{ ssl_fc }
        redirect scheme https code 301 if { hdr(Host) -i site5.example.com } !{ ssl_fc }

        # set default backend for non-matching hosts
        default_backend default_http

### frontend servers - HTTPS with SNI passthrough (your original approach)
frontend https_passthrough
        bind 0.0.0.0:443
        mode tcp
        option tcplog
        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }

        ## SNI-based routing - SSL passes through to backend servers
        use_backend site1_https_passthrough if { req.ssl_sni -i site1.example.com }
        use_backend site1_https_passthrough if { req.ssl_sni -i site1alias.example.com }
        use_backend site2_https_passthrough if { req.ssl_sni -i site2.example.com }
        use_backend site3_https_passthrough if { req.ssl_sni -i site3.example.com }
        use_backend site4_https_passthrough if { req.ssl_sni -i site4.example.com }

        # set default backend
        default_backend default_https_passthrough

### frontend servers - HTTPS with SSL termination at HAProxy
frontend https_terminated
        bind 0.0.0.0:8443 ssl crt /etc/haproxy/certs/site5.example.com.pem crt /etc/haproxy/certs/
        mode http
        option httplog
        option forwardfor
        
        # Add security headers
        http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
        http-response set-header X-Frame-Options "SAMEORIGIN"
        http-response set-header X-Content-Type-Options "nosniff"
        
        # Route based on Host header (after SSL termination)
        use_backend site5_http_backend if { hdr(Host) -i site5.example.com }
        
        # set default backend
        default_backend default_http_backend

### backend servers - HTTP backends
backend default_http
        mode http
        server nginx_lb_http unix@/run/nginx_lb_http.sock send-proxy weight 100 check

backend default_http_backend
        mode http
        option httpchk GET /health
        server web1 192.168.1.201:80 weight 100 check
        server web2 192.168.1.202:80 weight 100 check

### backend servers - HTTPS passthrough backends (your original approach)
backend default_https_passthrough
        mode tcp
        server nginx_lb_https unix@/run/nginx_lb_https.sock send-proxy weight 100 check

backend site1_https_passthrough
        mode tcp
        option ssl-hello-chk
        server server1 192.168.1.101:443 weight 100 check

backend site2_https_passthrough
        mode tcp
        option ssl-hello-chk
        server server2 192.168.1.102:443 weight 100 check

backend site3_https_passthrough
        mode tcp
        option ssl-hello-chk
        server server3 192.168.1.103:443 weight 100 check

backend site4_https_passthrough
        mode tcp
        option ssl-hello-chk
        server server4 192.168.1.104:443 weight 100 check

### backend servers - HTTP backends (for SSL terminated traffic)
backend site5_http_backend
        mode http
        option httpchk GET /health
        # Backend servers receive plain HTTP after SSL termination
        server web5a 192.168.1.105:80 weight 100 check
        server web5b 192.168.1.106:80 weight 100 check

### Example: Multiple SSL certificates with termination
frontend https_multi_cert_terminated
        # Bind with multiple certificates - HAProxy will select the right one based on SNI
        bind 0.0.0.0:9443 ssl crt /etc/haproxy/certs/site6.example.com.pem crt /etc/haproxy/certs/site7.example.com.pem
        mode http
        option httplog
        option forwardfor
        
        # Security headers
        http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
        
        # Route to different backends based on the original SNI/Host
        use_backend site6_http_backend if { hdr(Host) -i site6.example.com }
        use_backend site7_http_backend if { hdr(Host) -i site7.example.com }

backend site6_http_backend
        mode http
        balance roundrobin
        option httpchk GET /api/health
        server app6a 192.168.1.107:8080 weight 100 check
        server app6b 192.168.1.108:8080 weight 100 check

backend site7_http_backend
        mode http
        balance leastconn
        option httpchk GET /status
        server app7a 192.168.1.109:3000 weight 100 check
        server app7b 192.168.1.110:3000 weight 100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment