pqcstatus
Guide

Enable post-quantum TLS on nginx, Apache, HAProxy and Caddy

The directives, the prerequisites, and how to verify the change without breaking older clients.

· 8 min

Enabling hybrid post-quantum key exchange is usually a one-line change, provided the TLS library underneath supports ML-KEM. This guide covers the four most common servers. We tested the nginx configuration below against OpenSSL 3.5 in our own integration tests.

Step 0: check the TLS library

ML-KEM groups are built into OpenSSL 3.5 and later. Check the version your server actually uses, which may differ from the command-line tool:

openssl version
nginx -V 2>&1 | grep -o 'OpenSSL [0-9.]*'
apachectl -V | grep -i openssl
haproxy -vv | grep -i openssl

If the version is older than 3.5, upgrade the operating system packages or use a build linked against a recent OpenSSL. Debian 13 (trixie) ships OpenSSL 3.5, for example. Without it, the directives below will fail or be ignored.

nginx

In the server block (or http block), allow TLS 1.3 and put the hybrid group first, followed by classical fallbacks:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;

Despite its name, ssl_ecdh_curve sets the list of key exchange groups passed to OpenSSL. Reload with nginx -t && systemctl reload nginx.

Apache HTTP Server

With mod_ssl linked to OpenSSL 3.5 or later, pass the group list directly to OpenSSL:

SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLOpenSSLConfCmd Groups X25519MLKEM768:X25519:prime256v1

Place the directives in the virtual host or in the global SSL configuration, then run apachectl configtest and reload.

HAProxy

In the global section, set the default groups for all bind lines:

global
    ssl-default-bind-options ssl-min-ver TLSv1.2
    ssl-default-bind-curves X25519MLKEM768:X25519:P-256

You can also set curves on an individual bind line. HAProxy must be built with OpenSSL 3.5 or later; check haproxy -vv.

Caddy

Caddy uses the Go TLS stack, which enables X25519MLKEM768 by default from Go 1.24. Recent Caddy releases built with Go 1.24 or later therefore negotiate it without any configuration. If your Caddyfile restricts curves in a tls block, add the hybrid group explicitly:

tls {
    curves x25519mlkem768 x25519
}

Behind a CDN or a load balancer

Enable the group on the component that terminates TLS for visitors: the CDN, the cloud load balancer or the reverse proxy. Then do the same for the connection from that component to your origin servers, which is a separate TLS link and often forgotten.

Keep a fallback

Always keep a classical group such as X25519 after the hybrid one. Clients that do not support ML-KEM simply negotiate X25519, so nothing breaks. The hybrid ClientHello is about one kilobyte larger; a few old middleboxes have mishandled large handshakes in the past, which is another reason to keep the fallback and to monitor errors after the change.

Verify

openssl s_client -connect www.example.com:443 -servername www.example.com \
  -groups X25519MLKEM768 -brief </dev/null | grep 'Negotiated'

You should see Negotiated TLS1.3 group: X25519MLKEM768. You can also run the PQC Status scanner, which additionally checks TLS versions, cipher suites and the certificate chain. Record the change and its date in your cryptographic inventory.

Which group should you choose?

X25519MLKEM768 is the default choice: recommended by IANA and supported by all major browsers. If you follow ANSSI guidance for sensitive systems, which prefers the highest security level, add SecP384r1MLKEM1024 first for clients that support it. Pure ML-KEM groups without a classical component are not recommended by ANSSI or BSI, which both ask for hybrid mode.

Frequently Asked Questions

Will enabling ML-KEM break older browsers?

No, as long as a classical group such as X25519 stays in the list. Clients that do not know the hybrid group negotiate the classical one, exactly as before.

Do I need a new certificate to enable post-quantum TLS?

No. The key exchange is independent of the certificate. Your existing RSA or ECDSA certificate keeps working while the session keys are protected by the hybrid exchange.

Why is nothing negotiated after I changed the configuration?

Most often the server is linked to an OpenSSL version older than 3.5, or TLS 1.3 is disabled, or a CDN terminates TLS before your server. Check the library version and test the server directly.

Should I enable pure ML-KEM without X25519?

Not for general use. ANSSI and BSI recommend hybrid mode, which keeps classical security if a flaw were found in the new algorithm.