When implementing HAProxy as a reverse proxy for multiple HTTPS domains, a common challenge arises when trying to serve different SSL certificates for different domain names. The default behavior of HAProxy (prior to version 1.5) was to use the first certificate specified in the configuration for all SSL connections, regardless of the requested domain.
Modern versions of HAProxy (1.5+) support Server Name Indication (SNI), which allows the proxy to select the appropriate certificate based on the client's requested hostname. Here are the key approaches:
Method 1: SNI with Multiple crt Parameters
frontend https-in
bind :443 ssl crt /etc/ssl/private/example.com.pem crt /etc/ssl/private/api.example.com.pem
use_backend www_servers if { ssl_fc_sni www.example.com }
use_backend api_servers if { ssl_fc_sni api.example.com }
Method 2: Using Combined PEM Files
frontend https-in
bind :443 ssl crt /etc/ssl/private/combined/
use_backend www_servers if { ssl_fc_sni www.example.com }
use_backend api_servers if { ssl_fc_sni api.example.com }
Here's a complete working configuration that handles multiple SSL certificates correctly:
global
log /dev/log local0
log /dev/log local1 notice
maxconn 4096
tune.ssl.default-dh-param 2048
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
frontend http-in
bind *:80
redirect scheme https code 301 if !{ ssl_fc }
frontend https-in
bind *:443 ssl crt /etc/ssl/private/www.example.com.pem crt /etc/ssl/private/api.example.com.pem
http-request set-header X-Forwarded-Proto https if { ssl_fc }
acl host_www hdr(host) -i www.example.com
acl host_api hdr(host) -i api.example.com
use_backend www_servers if host_www
use_backend api_servers if host_api
backend www_servers
balance roundrobin
server web1 10.0.0.1:80 check
server web2 10.0.0.2:80 check
backend api_servers
server api1 10.0.1.1:8080 check
server api2 10.0.1.2:8080 check
Each certificate file must contain:
-----BEGIN RSA PRIVATE KEY-----
[private key content]
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
[primary certificate content]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[intermediate CA certificate]
-----END CERTIFICATE-----
To verify that the correct certificates are being served:
openssl s_client -connect www.example.com:443 -servername www.example.com | openssl x509 -noout -subject
openssl s_client -connect api.example.com:443 -servername api.example.com | openssl x509 -noout -subject
When using multiple certificates:
- Keep your certificate chain optimized (include only necessary intermediates)
- Use elliptic curve certificates for better performance
- Consider OCSP stapling to reduce handshake time
If certificates aren't being served correctly:
- Verify HAProxy version supports SNI (1.5+)
- Check certificate file permissions (haproxy user needs read access)
- Confirm the certificate files contain both private key and full chain
- Test with OpenSSL commands before troubleshooting browser behavior
When handling multiple domains with distinct SSL certificates in HAProxy, the key is proper Server Name Indication (SNI) implementation. Your current configuration applies the first certificate to all requests because HAProxy needs explicit domain-to-certificate mapping.
For HAProxy 1.5+ with OpenSSL 1.0.2+, use this optimized configuration:
frontend https-in
bind :443 ssl crt /etc/haproxy/certs/ alpn h2,http/1.1
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
# Certificate per domain
use_backend www_backend if { ssl_fc_sni www.example.com }
use_backend api_backend if { ssl_fc_sni api.example.com }
Organize certificates properly for HAProxy's automatic loading:
/etc/haproxy/certs/
├── www.example.com.pem
├── api.example.com.pem
└── dhparams.pem
Each .pem file should contain (in order):
- Private key
- Certificate
- Intermediate certificates
Here's a production-ready configuration:
global
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11
tune.ssl.default-dh-param 2048
frontend https_frontend
bind :443 ssl crt /etc/haproxy/certs/ strict-sni
# ACLs for SNI matching
acl is_www hdr(host) -i www.example.com
acl is_api hdr(host) -i api.example.com
# Backend selection
use_backend www_servers if is_www
use_backend api_servers if is_api
# HTTP to HTTPS redirect
redirect scheme https code 301 if !{ ssl_fc }
backend www_servers
server server1 10.0.1.10:443 check ssl verify none
backend api_servers
server server1 10.0.2.10:443 check ssl verify none
Use these OpenSSL commands to verify proper certificate serving:
# Test www domain
openssl s_client -connect your.haproxy:443 -servername www.example.com | openssl x509 -noout -subject
# Test api domain
openssl s_client -connect your.haproxy:443 -servername api.example.com | openssl x509 -noout -subject
When dealing with multiple certificates:
- Enable SSL session caching (
ssl-default-server-cache
) - Consider OCSP stapling with
ssl ocsp-response
- Enable SSL compression only if absolutely necessary