Modern network architectures often require sophisticated routing of encrypted traffic. While HAProxy is renowned for HTTP/HTTPS load balancing, its capabilities extend significantly into Layer 4 TCP proxying, including SSL/TLS passthrough for non-HTTP protocols. This mode allows backend servers to handle SSL/TLS termination directly, which is crucial for end-to-end encryption integrity or when backend services manage their own certificates for protocols like MQTT, XMPP, secure email (IMAPS, SMTPS), or other custom TCP services.
A key challenge in such scenarios is directing incoming TLS connections to the correct backend service pool when multiple services share a single public IP address and port (e.g., port 443). Server Name Indication (SNI), an extension to the TLS protocol defined in RFC 6066, provides the solution by allowing the client to specify the hostname it’s trying to reach during the TLS handshake. HAProxy can inspect this SNI value without decrypting the traffic and make intelligent routing decisions.
This article provides a comprehensive guide for configuring HAProxy to perform SSL/TLS passthrough with SNI-based backend selection, focusing specifically on non-HTTP protocols. We’ll cover the core concepts, detailed configuration steps, practical examples, testing methods, and common pitfalls.
Core Concepts Explained
Before diving into the configuration, let’s clarify the key technologies:
- HAProxy: A high-performance, open-source TCP/HTTP load balancer and proxy server.
- SSL/TLS Passthrough: HAProxy forwards encrypted traffic directly to backend servers without decrypting it. The TLS handshake occurs between the client and the backend server.
- SNI (Server Name Indication): A TLS extension where the client indicates the hostname it wishes to connect to. This information is in the ClientHello message, which is unencrypted at the start of the TLS handshake.
- Non-HTTP Protocols: Any TCP-based application protocol other than HTTP/S, such as MQTT, XMPP, IMAPS, SMTPS, FTPS, or custom binary protocols that use TLS for security.
mode tcp
: HAProxy’s operational mode for Layer 4 load balancing, essential for SSL/TLS passthrough. See HAProxy Configuration Manual - mode.req_ssl_sni
: An HAProxy fetch method to extract the SNI hostname from the client’s TLS handshake. See HAProxy Configuration Manual - req_ssl_sni.
Why Use SSL/TLS Passthrough with SNI?
This configuration offers several advantages:
- End-to-End Encryption: The proxy does not decrypt the traffic, preserving a true end-to-end encrypted channel between the client and the backend server.
- Simplified Certificate Management on Proxy: SSL/TLS certificates and private keys are managed solely on the backend servers. HAProxy doesn’t need access to them for passthrough.
- Support for Non-HTTP Protocols: Effectively routes various TLS-secured services that are not HTTP-based.
- Resource Efficiency on Proxy: SSL/TLS passthrough consumes fewer CPU resources on HAProxy compared to SSL/TLS termination, as no cryptographic operations are performed by the proxy.
- Centralized Entry Point: Multiple distinct services, each with its own TLS certificate and hostname, can share a single public IP address and port.
Prerequisites
- HAProxy Version: HAProxy 1.5 or later is required for SNI support in
mode tcp
. Using a recent stable version (e.g., 2.4, 2.6, 2.8 or newer, available from the HAProxy Download Page) is highly recommended for the latest features and security fixes. - Backend Server Configuration: Backend servers must be configured to handle their respective non-HTTP protocols and perform SSL/TLS termination using appropriate certificates for the hostnames they serve.
- Understanding of TCP and TLS: Familiarity with TCP/IP networking and the TLS handshake process is beneficial.
HAProxy Configuration Steps
Configuring HAProxy for SNI-based SSL/TLS passthrough involves several key directives primarily within a frontend
section operating in mode tcp
.
1. Set mode tcp
The frontend handling the incoming TLS connections must operate in mode tcp
.
|
|
This configuration makes HAProxy listen on port 443 for any TCP traffic.
2. Inspect ClientHello for SNI
To reliably capture the SNI, HAProxy needs to wait for the client to send its ClientHello message. This is achieved using tcp-request inspect-delay
and tcp-request content accept
.
|
|
Without inspect-delay
, HAProxy might try to read the SNI before the client has sent enough data. req_ssl_hello_type 1
ensures HAProxy processes data only when it identifies a ClientHello message. The condition { req_ssl_hello_type 1 }
checks if the SSL record type is ClientHello.
3. Define ACLs Based on SNI
Access Control Lists (ACLs) are used to match the SNI value extracted by req_ssl_sni
. See HAProxy Configuration Manual - acl.
|
|
The -i
flag makes the hostname match case-insensitive. The -m end
flag performs a suffix match. Other match types like -m beg
(prefix) or -m sub
(substring) are also available.
4. Route Traffic Using use_backend
Based on the ACLs, use_backend
directives route the connection to the appropriate backend server pool. The order matters; the first matching use_backend
rule is applied.
|
|
5. Specify a default_backend
A default_backend
handles connections that do not match any SNI ACLs or for clients that do not send SNI.
|
|
This backend could route to a default service, an error page, or simply drop the connection depending on policy.
6. Configure Backend Server Pools
Each backend pool defines the servers for a specific service. They must also operate in mode tcp
.
|
|
Backend servers are responsible for their specific non-HTTP protocol and SSL/TLS termination on their respective ports (e.g., 8883
for secure MQTT, 5223
for secure XMPP client-to-server). The check
option enables basic TCP health checks.
Complete HAProxy Configuration Example
Here’s a consolidated example for haproxy.cfg
:
|
|
Remember to adjust IP addresses, ports, hostnames, and server names according to your specific environment. Also, ensure paths for chroot
, stats socket
, and errorfile
match your HAProxy installation.
Testing the Configuration
The openssl s_client
command-line tool is invaluable for testing SNI-based routing. To test connectivity to mqtt.example.com
through HAProxy (assuming HAProxy is at your_haproxy_ip
):
|
|
This command attempts a TLS connection to your_haproxy_ip:443
and passes mqtt.example.com
as the SNI.
Key things to check in the output:
- Successful Connection: Indicated by
Verification: OK
or similar, and session details. - Certificate Details: The certificate presented should be from the
mqtt.example.com
backend server, matching the SNI. - Protocol Handshake: After the TLS handshake, you might be able to send protocol-specific commands if you know them.
Repeat the test for each configured SNI hostname (e.g., xmpp.chat.org
, imap.example.org
) to ensure they are routed to the correct backends.
Logging SNI Information
To log the SNI value captured by HAProxy, you can customize the log format. See HAProxy Configuration Manual - log-format. First, capture the SNI value in the frontend:
|
|
Then, use capture.req.hdr(0)
(or the specific capture slot) in your log-format
string in the defaults
or global
section. This example log-format
string might be too long for a single line in this document, but would be a single line in haproxy.cfg
. For readability here, it’s wrapped:
|
|
HAProxy versions 1.7+ also allow direct use of %{+Q}[req.ssl_sni]
in the log format:
|
|
This allows you to see which SNI is being presented for each connection in your HAProxy logs.
Common Challenges and Pitfalls
- Missing
tcp-request inspect-delay
: If HAProxy doesn’t wait long enough, it may not receive the full ClientHello and thus fail to extract SNI, leading to connections falling through to thedefault_backend
. - Missing
tcp-request content accept if { req_ssl_hello_type 1 }
: Without this, HAProxy might try to parse SNI from non-ClientHello TCP packets, causing errors or misrouting. - Client Not Sending SNI: Older or misconfigured clients might not send SNI. These connections will be routed to the
default_backend
. - Incorrect SNI in ACLs: Typos or case-mismatches (if
-i
is not used) in hostnames in ACL definitions. - Backend Server Misconfiguration:
- Servers not listening on the correct IP/port.
- Servers not configured for SSL/TLS.
- SSL/TLS certificates on backend servers not matching the SNI hostname, or being expired/invalid, causing client-side errors.
- The specific non-HTTP application on the backend not functioning correctly.
- Firewall Rules: Firewalls blocking traffic between clients and HAProxy, or between HAProxy and backend servers.
- SSL/TLS Termination Attempted on HAProxy: Adding
ssl crt
to thebind
line in the passthrough frontend will cause HAProxy to attempt termination, which conflicts with the passthrough goal.
Advanced Considerations
- Encrypted SNI (ESNI) / Encrypted Client Hello (ECH): These emerging TLS features (ECH is specified in RFC 9520 which obsoletes earlier ESNI drafts) encrypt the SNI value itself. If clients use ECH widely, HAProxy (and other proxies relying on SNI inspection for passthrough) will not be able to read the SNI, breaking this routing method. This is a future consideration; currently, SNI is generally visible.
- ALPN (Application-Layer Protocol Negotiation): Clients can also indicate the application protocol they wish to use (e.g.,
mqtt
,xmpp-client
) via the ALPN TLS extension (RFC 7301). HAProxy can inspect this usingreq.ssl_alpn
and use it for routing decisions, often in conjunction with SNI.1 2
acl is_mqtt_protocol req.ssl_alpn -i mqtt use_backend bk_mqtt_specific if mqtt_traffic and is_mqtt_protocol
- PROXY Protocol: To pass the original client’s IP address to backend servers (which is otherwise lost), enable the PROXY protocol on HAProxy for the server lines and ensure backend services support it.
1 2 3 4
backend bk_mqtt_passthrough mode tcp # ... server mqtt_server1 10.0.1.10:8883 check send-proxy-v2
Conclusion
Configuring HAProxy for SSL/TLS passthrough with SNI-based backend selection provides a powerful and flexible way to route various encrypted non-HTTP protocols. By operating in mode tcp
and correctly inspecting the SNI from the unencrypted ClientHello, HAProxy can direct traffic to appropriate backend services without needing to decrypt it, preserving end-to-end encryption and simplifying certificate management on the proxy.
This setup is ideal for environments hosting multiple distinct TLS-secured services like IoT platforms (MQTT), chat servers (XMPP), or secure email relays, all consolidated behind a single public-facing IP address and port. Careful attention to the tcp-request inspect-delay
and tcp-request content accept
directives, along with accurate ACLs and robust backend configurations, is key to a successful and secure deployment.