How to Enable X25519MLKEM768 in Nginx and OpenSSL 3.5
A step-by-step guide to enabling post-quantum hybrid key exchange on your Nginx server with OpenSSL 3.5 — including how to verify it and common pitfalls.
The single highest-impact, lowest-risk step in a post-quantum migration is enabling hybrid key exchange on your TLS endpoints. This guide shows you how to turn on X25519MLKEM768 in Nginx with OpenSSL 3.5, how to verify it's actually being negotiated, and the two pitfalls that trip most people up.
If you want the bigger picture first, see our Post-Quantum Cryptography Migration Guide. Otherwise, let's get into the config.
What X25519MLKEM768 is, in one paragraph
X25519MLKEM768 is a hybrid key-exchange group: it runs classical X25519 and post-quantum ML-KEM-768 together and combines both shared secrets. The connection is secure as long as either algorithm holds, so you get post-quantum protection today without betting everything on a young algorithm. It's the group Chrome, Firefox, Cloudflare and others have deployed in production. Enabling it protects your traffic against "harvest now, decrypt later" interception immediately.
Prerequisite: OpenSSL 3.5+
This is the part that blocks most people. Native ML-KEM support landed in OpenSSL 3.5.0, released April 8, 2025 as a long-term-support release. If your Nginx is linked against OpenSSL 3.4 or earlier, you cannot offer ML-KEM no matter what you put in the config.
Check what Nginx is actually linked against:
nginx -V 2>&1 | grep -i openssl
Note that the OpenSSL version Nginx was built with matters, not just the one installed on the system. If you're on an older build, you'll need to upgrade your OpenSSL and rebuild or update Nginx (or use a distro package that ships 3.5+).
The Nginx configuration
With OpenSSL 3.5+ in place, you set the curve/group preference list in your server or http block. Put the hybrid group first so it's preferred, with classical groups after it as fallback:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:secp256r1;
That ordering means: prefer the post-quantum hybrid, but fall back cleanly to X25519 or P-256 for clients that don't support it yet. Nothing breaks for older clients — they simply negotiate a classical group as before.
If you manage TLS settings through ssl_conf_command instead, the equivalent is:
ssl_conf_command Groups X25519MLKEM768:X25519:secp256r1;
Reload Nginx to apply:
nginx -t && nginx -s reload
Verify it's actually working
Don't trust the config — verify the handshake. With an OpenSSL 3.5+ client:
openssl s_client -connect yourdomain.com:443 -groups X25519MLKEM768 </dev/null 2>/dev/null | grep -i "Negotiated"
If the negotiated group comes back as X25519MLKEM768, you're done. You can also confirm from the outside with a scanner that understands post-quantum groups — run a free PQScore scan and check the Key Exchange result.
Two pitfalls that cause false results
1. A middlebox is stripping the PQ group. Some inline security appliances and load balancers that do TLS inspection will strip the post-quantum group from the ClientHello, so your origin never sees it. The symptom: your config is correct, the origin supports it, but external probes report no PQ support. If you're behind a TLS-inspecting proxy or older WAF, check whether it understands X25519MLKEM768 and passes it through.
2. You're behind a CDN. If a CDN terminates TLS in front of you, then the CDN's TLS configuration — not yours — is what clients negotiate. Enabling the group on your origin won't change what visitors see. The good news is that most major CDNs already support post-quantum key exchange; the fix is enabling it in the CDN dashboard, not on your origin.
What this does and doesn't cover
Enabling X25519MLKEM768 protects your key exchange — the "harvest now, decrypt later" half of the problem. It does not make your certificate signatures post-quantum; that's a separate migration to ML-DSA that depends on your certificate authority and is a longer track. For the full sequence — inventory, key exchange, then PKI — see the migration guide.
Want to confirm your change worked from the outside? Scan your domain free with PQScore →