The prerequisites
Hybrid post-quantum key exchange needs two things: TLS 1.3 (there is no post-quantum TLS 1.2), and a TLS library that knows the ML-KEM groups. In the OpenSSL world that means OpenSSL 3.5 or newer — the current long-term-support release, which ships ML-KEM natively and even offers X25519MLKEM768 by default. If your distribution still ships an older OpenSSL, upgrading the library (or terminating TLS somewhere newer) is the whole job; no certificate changes are involved.
Behind a CDN? You may already be done
- Cloudflare has offered post-quantum key exchange at its edge for years, on for all plans. If Cloudflare terminates your TLS, browser-to-edge traffic is already hybrid — worth confirming with a scan rather than assuming. For full coverage, Cloudflare can also speak post-quantum on the edge-to-origin leg if your origin supports it.
- Other CDNs and load balancers (Fastly, AWS, Google Cloud) support ML-KEM hybrids across most TLS-terminating products now — check the product notes for your specific termination point.
The general rule: post-quantum key exchange is a property of whatever terminates TLS. Fix it there, and remember internal legs (CDN → origin, service → service) are separate conversations with their own HNDL exposure.
Self-hosted stacks
nginx (with OpenSSL 3.5+): set the group list, hybrid first, classical fallbacks after:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1; Apache httpd (with OpenSSL 3.5+):
SSLOpenSSLConfCmd Groups X25519MLKEM768:X25519:prime256v1 HAProxy:
ssl-default-bind-curves X25519MLKEM768:X25519:P-256 Caddy / Go servers: nothing to do — Go's TLS stack has enabled X25519MLKEM768 by default since Go 1.24, so recent builds negotiate it automatically.
Node.js: builds against OpenSSL 3.5 accept the same group list via --tls-groups or the ecdhCurve option.
Keep classical groups in the list. Clients that don't offer ML-KEM (older browsers, embedded devices, many API clients) must still be able to fall back to X25519 or P-256 — the hybrid group protects the clients that ask for it without breaking the ones that can't.
Verifying it worked
The quickest check is the post-quantum TLS test — it pins a separate handshake to each ML-KEM group and shows exactly which ones your server accepts. From a machine with OpenSSL 3.5+, you can do the same by hand:
openssl s_client -connect example.com:443 -groups X25519MLKEM768 -brief A successful connection means the server negotiated the hybrid group (the pinned offer leaves it nothing else to choose); a handshake failure means it doesn't support it. In Chrome, DevTools → Security shows the key exchange as "X25519MLKEM768" on protected sites.
What you do not need to change
- Certificates. Your RSA or ECDSA certificate is untouched by all of this — post-quantum certificates are a separate, later migration.
- Ciphers. AES and ChaCha20 are already quantum-resistant at TLS 1.3 key sizes.
- Application code. Key exchange is negotiated below the application layer; nothing above TLS notices.
Made a change? Re-run the post-quantum TLS test to confirm, and check the rest of your protocol hygiene with the TLS version scanner.