Mutual TLS is surprisingly less about security and more about identity management for your services.

Let’s see HAProxy handle client authentication using mutual TLS.

frontend my_frontend
    bind *:443 ssl crt /etc/haproxy/certs/server.pem ca-file /etc/haproxy/certs/ca.pem verify required

    acl host_matches hdr(host) -i example.com
    use_backend my_backend if host_matches

backend my_backend
    server s1 192.168.1.100:80 check
    server s2 192.168.1.101:80 check

Here’s what’s happening:

  • bind *:443 ssl crt /etc/haproxy/certs/server.pem ca-file /etc/haproxy/certs/ca.pem verify required: This is the core of mutual TLS.

    • ssl: Enables SSL/TLS for this frontend.
    • crt /etc/haproxy/certs/server.pem: Specifies the server’s private key and certificate chain. This is what HAProxy presents to the client.
    • ca-file /etc/haproxy/certs/ca.pem: This file contains the Certificate Authority (CA) certificates that HAProxy will use to verify the client’s certificate.
    • verify required: This is the crucial part for mutual TLS. It tells HAProxy to require the client to present a valid certificate signed by one of the CAs listed in ca-file. If the client doesn’t present a certificate, or if it’s not signed by a trusted CA, the connection will be dropped.
  • acl host_matches hdr(host) -i example.com: This is a standard HAProxy ACL to match incoming requests based on the Host header. It ensures that only requests for example.com are processed by this frontend.

  • use_backend my_backend if host_matches: This directs traffic matching the host_matches ACL to the my_backend backend.

  • backend my_backend: Defines the backend servers.

    • server s1 192.168.1.100:80 check: Configures the first backend server, s1, and enables health checks.
    • server s2 192.168.1.101:80 check: Configures the second backend server, s2.

The overall goal is to establish a secure TLS connection where both the server (HAProxy) and the client authenticate each other. HAProxy presents its certificate to the client, and in turn, expects the client to present its own certificate. HAProxy then validates this client certificate against a trusted CA. This ensures that only clients possessing a valid, trusted certificate can connect.

To make this work, you need a few things:

  1. Server Certificate and Key: A certificate (server.pem) that HAProxy will use to identify itself to clients. This certificate should be signed by a CA that clients trust, or it can be self-signed for testing.
  2. Client CA Certificate: A Certificate Authority file (ca.pem) that contains the CA certificate(s) that signed the client certificates. HAProxy uses this to verify client authenticity.
  3. Client Certificate and Key: Each client that needs to connect must have its own unique certificate, signed by the same CA whose certificate is in ca.pem. This client certificate is what the client presents to HAProxy during the TLS handshake.

When a client initiates a connection to HAProxy on port 443:

  1. HAProxy presents its server.pem certificate.
  2. The client verifies HAProxy’s certificate.
  3. HAProxy then requests a client certificate.
  4. The client presents its certificate.
  5. HAProxy uses the ca.pem file to verify that the client’s certificate is valid and was issued by a trusted CA.
  6. If verification succeeds, the TLS handshake completes, and the connection proceeds. If not, the connection is terminated.

One aspect that often trips people up is the distinction between the crt file and the ca-file. The crt file (or crt and key separately) is for HAProxy’s own identity. The ca-file is for verifying the other party’s identity. In mutual TLS, both are used, but for different purposes. The ca-file in the bind line is specifically for verifying the client’s certificate.

After configuring mutual TLS, the next logical step is to inspect the client certificate’s details within HAProxy to make authorization decisions based on its contents.

Want structured learning?

Take the full Haproxy course →